diff --git a/README.md b/README.md index 08f6d23f..2c8c88d5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,41 @@ +### Change log [2025-07-30 18:04:17] +1. Item Updated: `model_server_tester` (from version: `1.1.0` to `1.1.0`) +2. Item Updated: `aggregate` (from version: `1.3.0` to `1.3.0`) +3. Item Updated: `translate` (from version: `0.2.0` to `0.2.0`) +4. Item Updated: `v2_model_server` (from version: `1.2.0` to `1.2.0`) +5. Item Updated: `gen_class_data` (from version: `1.3.0` to `1.3.0`) +6. Item Updated: `auto_trainer` (from version: `1.7.0` to `1.7.0`) +7. Item Updated: `silero_vad` (from version: `1.4.0` to `1.4.0`) +8. Item Updated: `text_to_audio_generator` (from version: `1.3.0` to `1.3.0`) +9. Item Updated: `describe` (from version: `1.3.0` to `1.3.0`) +10. Item Updated: `transcribe` (from version: `1.2.0` to `1.2.0`) +11. Item Updated: `pyannote_audio` (from version: `1.3.0` to `1.3.0`) +12. Item Updated: `test_classifier` (from version: `1.1.0` to `1.1.0`) +13. Item Updated: `feature_selection` (from version: `1.6.0` to `1.6.0`) +14. Item Updated: `tf2_serving` (from version: `1.1.0` to `1.1.0`) +15. Item Updated: `azureml_serving` (from version: `1.1.0` to `1.1.0`) +16. Item Updated: `sklearn_classifier` (from version: `1.1.1` to `1.1.1`) +17. Item Updated: `azureml_utils` (from version: `1.4.0` to `1.4.0`) +18. Item Updated: `describe_dask` (from version: `1.1.0` to `1.1.0`) +19. Item Updated: `mlflow_utils` (from version: `1.1.0` to `1.1.0`) +20. Item Updated: `github_utils` (from version: `1.1.0` to `1.1.0`) +21. Item Updated: `v2_model_tester` (from version: `1.1.0` to `1.1.0`) +22. Item Updated: `open_archive` (from version: `1.2.0` to `1.2.0`) +23. Item Updated: `describe_spark` (from version: `1.1.0` to `1.1.0`) +24. Item Updated: `sklearn_classifier_dask` (from version: `1.1.1` to `1.1.1`) +25. Item Updated: `batch_inference_v2` (from version: `2.6.0` to `2.6.0`) +26. Item Updated: `arc_to_parquet` (from version: `1.5.0` to `1.5.0`) +27. Item Updated: `send_email` (from version: `1.2.0` to `1.2.0`) +28. Item Updated: `structured_data_generator` (from version: `1.6.0` to `1.6.0`) +29. Item Updated: `question_answering` (from version: `0.5.0` to `0.5.0`) +30. Item Updated: `hugging_face_serving` (from version: `1.1.0` to `1.1.0`) +31. Item Updated: `noise_reduction` (from version: `1.1.0` to `1.1.0`) +32. Item Updated: `pii_recognizer` (from version: `0.4.0` to `0.4.0`) +33. Item Updated: `onnx_utils` (from version: `1.3.0` to `1.3.0`) +34. Item Updated: `batch_inference` (from version: `1.8.0` to `1.8.0`) +35. Item Updated: `load_dataset` (from version: `1.2.0` to `1.2.0`) +36. Item Updated: `model_server` (from version: `1.1.0` to `1.1.0`) + ### Change log [2025-07-27 06:51:56] 1. Item Updated: `model_server_tester` (from version: `1.1.0` to `1.1.0`) 2. Item Updated: `aggregate` (from version: `1.3.0` to `1.3.0`) diff --git a/catalog.json b/catalog.json index dcaef5e3..d52626bc 100644 --- a/catalog.json +++ b/catalog.json @@ -1 +1 @@ -{"functions": {"development": {"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0"}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.7.1", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.2"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false}, "0.9.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.3"}, "1.4.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "0.10.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "1.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "1.0.1"}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3"}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2"}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "1.0.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.4"}, "1.0.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.2"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.10.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.1.0": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0"}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false}, "1.2.0": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0"}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1"}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0"}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0"}, "0.5.0": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false}, "0.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0"}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0"}, "2.3.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.3.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0"}, "2.6.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.2.0": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.3.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.1"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0"}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pyTorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0"}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0"}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0"}}}, "master": {"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0"}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0"}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1"}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1"}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0"}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5"}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0"}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "1.0.7": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.7"}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6"}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.0.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.5"}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.4.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.3.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.1"}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["huggingface", "genai", "model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.1.0": {"apiVersion": "v1", "categories": ["huggingface", "genai", "model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0"}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0"}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0"}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0"}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.1.0"}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0"}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1"}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0"}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.0.1"}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0"}, "2.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "2.4.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.4.0"}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0"}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0"}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0"}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0"}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0"}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0"}}}}} \ No newline at end of file +{"functions": {"development": {"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0"}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.7.1", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.2"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false}, "0.9.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.3"}, "1.4.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "0.10.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "1.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "1.0.1"}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3"}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2"}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "1.0.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.4"}, "1.0.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.2"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.10.1": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.1.0": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0"}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false}, "1.2.0": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0"}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1"}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0"}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0"}, "0.5.0": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false}, "0.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0"}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0"}, "2.3.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.3.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0"}, "2.6.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.2.0": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.3.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.1"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0"}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pyTorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0"}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0"}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0"}}}, "master": {"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0"}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1"}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0"}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1"}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0"}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0"}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0"}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0"}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false}, "1.4.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0"}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3"}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2"}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0"}, "1.0.7": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.7"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0"}, "1.0.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.5"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0"}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0"}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.3.0"}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.1"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.1.0": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0"}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0"}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1"}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0"}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.1.0"}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0"}, "0.5.0": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0"}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0"}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false}, "1.2.0": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0"}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.0.1"}, "0.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0"}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0"}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0"}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0"}, "2.4.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.4.0"}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0"}, "2.6.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0"}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0"}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.2.0": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.6.0": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0"}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0"}, "1.4.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0"}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0"}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0"}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0"}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0"}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0"}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0"}}}}} \ No newline at end of file diff --git a/functions/master/_static/mystnb.8ecb98da25f57f5357bf6f572d296f466b2cfe2517ffebfabe82451661e28f02.css b/functions/master/_static/mystnb.8ecb98da25f57f5357bf6f572d296f466b2cfe2517ffebfabe82451661e28f02.css new file mode 100644 index 00000000..14edf629 --- /dev/null +++ b/functions/master/_static/mystnb.8ecb98da25f57f5357bf6f572d296f466b2cfe2517ffebfabe82451661e28f02.css @@ -0,0 +1,2474 @@ +/* Variables */ +:root { + /* + Following palettes are generated by using https://m2.material.io/design/color/the-color-system.html#tools-for-picking-colors + - neutral palette with #fcfcfc and danger palette with #ffdddd as base colors. + 50 means lightest, 900 means darkest; less used intermediate shades are omitted + but can be added when needed by accessing full palette from the above link. + */ + --mystnb-neutral-palette-50: #fcfcfc; + --mystnb-neutral-palette-100: #f7f7f7; + --mystnb-neutral-palette-400: #cccccc; + --mystnb-neutral-palette-500: #afafaf; + --mystnb-neutral-palette-800: #505050; + --mystnb-neutral-palette-900: #2d2d2d; + + --mystnb-danger-palette-50: #ffdddd; + --mystnb-danger-palette-100: #f5acad; + --mystnb-danger-palette-400: #c42029; + --mystnb-danger-palette-500: #b40008; + --mystnb-danger-palette-800: #850010; + --mystnb-danger-palette-900: #680010; + + /* MyST-NB specific variables; colors should be logically picked from palettes */ + --mystnb-source-bg-color: var(--mystnb-neutral-palette-100); + --mystnb-stdout-bg-color: var(--mystnb-neutral-palette-50); + --mystnb-stderr-bg-color: var(--mystnb-danger-palette-50); + --mystnb-traceback-bg-color: var(--mystnb-neutral-palette-50); + --mystnb-source-border-color: var(--mystnb-neutral-palette-400); + --mystnb-source-margin-color: green; + --mystnb-stdout-border-color: var(--mystnb-neutral-palette-100); + --mystnb-stderr-border-color: var(--mystnb-neutral-palette-100); + --mystnb-traceback-border-color: var(--mystnb-danger-palette-100); + --mystnb-hide-prompt-opacity: 70%; + --mystnb-source-border-radius: .4em; + --mystnb-source-border-width: 1px; + --mystnb-scrollbar-width: 0.3rem; + --mystnb-scrollbar-height: 0.3rem; + --mystnb-scrollbar-thumb-color: var(--mystnb-neutral-palette-400); + --mystnb-scrollbar-thumb-hover-color: var(--mystnb-neutral-palette-500); + --mystnb-scrollbar-thumb-border-radius: 0.25rem; +} + +/* Override colors in dark theme */ +html[data-theme="dark"] { + --mystnb-source-bg-color: var(--mystnb-neutral-palette-800); + --mystnb-stdout-bg-color: var(--mystnb-neutral-palette-900); + --mystnb-stderr-bg-color: var(--mystnb-danger-palette-900); + --mystnb-traceback-bg-color: var(--mystnb-neutral-palette-900); + --mystnb-source-border-color: var(--mystnb-neutral-palette-500); + --mystnb-stdout-border-color: var(--mystnb-neutral-palette-800); + --mystnb-stderr-border-color: var(--mystnb-neutral-palette-800); + --mystnb-traceback-border-color: var(--mystnb-danger-palette-800); + --mystnb-scrollbar-thumb-color: var(--mystnb-neutral-palette-500); + --mystnb-scrollbar-thumb-hover-color: var(--mystnb-neutral-palette-400); +} + + +/* Whole cell */ +div.container.cell { + padding-left: 0; + margin-bottom: 1em; +} + +/* Removing all background formatting so we can control at the div level */ +.cell_input div.highlight, +.cell_output pre, +.cell_input pre, +.cell_output .output { + border: none; + box-shadow: none; +} + +.cell_output .output pre, +.cell_input pre { + margin: 0px; +} + +/* Input cells */ +div.cell > div.cell_input { + padding-left: 0em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + background-color: var(--mystnb-source-bg-color); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; + border-radius: var(--mystnb-source-border-radius); +} + +div.cell_input>div, +div.cell_output div.output>div.highlight { + margin: 0em !important; + border: none !important; +} + +/* All cell outputs */ +.cell_output { + padding-left: 1em; + padding-right: 0em; + margin-top: 1em; +} + +/* Text outputs from cells */ +.cell_output .output.text_plain, +.cell_output .output.traceback, +.cell_output .output.stream, +.cell_output .output.stderr { + margin-top: 1em; + margin-bottom: 0em; + box-shadow: none; +} + +.cell_output .output.text_plain:not(:has(.highlight)), +.cell_output .output.stream:not(:has(.highlight)) { + /* plain (or stream of) output, not containing a pygments-highlighted block */ + background: var(--mystnb-stdout-bg-color); + border: 1px solid var(--mystnb-stdout-border-color); +} + +.cell_output .output.stderr { + background: var(--mystnb-stderr-bg-color); + border: 1px solid var(--mystnb-stderr-border-color); +} + +.cell_output .output.traceback { + background: var(--mystnb-traceback-bg-color); + border: 1px solid var(--mystnb-traceback-border-color); +} + +/* --- Collapsible cell content --- */ + +/* +encourage summary container to blend in with its parent. +p.admonition-title should hold the title styles. +*/ +div.cell details.hide summary { + border-left: unset; + padding: inherit; + margin: inherit; + background-color: inherit; +} + +/* Neighboring input/output elements - spacing, borders */ +div.cell details.hide.above-input + details.below-input, +div.cell div.cell_input + details.below-input +{ + margin-top: 0; +} + +div.cell details.hide.above-input:has(+ details.below-input), +div.cell div.cell_input:has(+ details.below-input) +{ + margin-bottom: 0; +} + +div.cell:has(> *:nth-child(2)) div.cell_input:first-child, +div.cell:has(> *:nth-child(2)) details:first-child +{ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.cell:has(> *:nth-child(2)) div.cell_input:last-child, +div.cell:has(> *:nth-child(2)) details:last-child +{ + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +/* intra-label styles for collapsibles */ +div.cell.container details.hide.above-input>summary, +div.cell.container details.hide.below-input>summary, +div.cell.container details.hide.above-output>summary +{ + display: block; + border-left: none; +} + +div.cell details.hide>summary>p.admonition-title { + display: list-item; + margin-bottom: 0; +} + +div.cell details.hide:not([open]) { + padding-bottom: 0; +} + +div.cell details.hide[open]>summary>p.collapsed { + display: none; +} + +div.cell details.hide:not([open])>summary>p.expanded { + display: none; +} + +@keyframes collapsed-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} +div.cell details.hide[open]>summary~* { + -moz-animation: collapsed-fade-in 0.3s ease-in-out; + -webkit-animation: collapsed-fade-in 0.3s ease-in-out; + animation: collapsed-fade-in 0.3s ease-in-out; +} + +/* Clear conflicting styles for details and admonitions set by some themes */ +div.cell details.admonition summary::before { + content: unset; +} + +/* Math align to the left */ +.cell_output .MathJax_Display { + text-align: left !important; +} + +/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */ +div.cell_output table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 1em; + table-layout: fixed; +} + +div.cell_output thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} + +div.cell_output tr, +div.cell_output th, +div.cell_output td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +div.cell_output th { + font-weight: bold; +} + +div.cell_output tbody tr:nth-child(odd) { + background: #f5f5f5; +} + +div.cell_output tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + +/** source code line numbers **/ +span.linenos { + opacity: 0.5; +} + +/* Inline text from `paste` operation */ + +span.pasted-text { + font-weight: bold; +} + +span.pasted-inline img { + max-height: 2em; +} + +tbody span.pasted-inline img { + max-height: none; +} + + +/* Adding scroll bars if tags: output_scroll, scroll-output, and scroll-input + * On screens, we want to scroll, but on print show all + * + * It was before in https://github.com/executablebooks/sphinx-book-theme/blob/eb1b6baf098b27605e8f2b7b2979b17ebf1b9540/src/sphinx_book_theme/assets/styles/extensions/_myst-nb.scss +*/ +div.cell:is( + .tag_output_scroll, + .tag_scroll-output, + .config_scroll_outputs + ) + div.cell_output, +div.cell.tag_scroll-input div.cell_input { + max-height: 24em; + overflow-y: auto; + max-width: 100%; + overflow-x: auto; +} + +div.cell.config_scroll_outputs div.cell_output:has(img) { + /* If the output cell has image(s), allow it to take 90% of viewport height + but still bounded between 24em and 60em */ + max-height: clamp(24em, 90vh, 60em); +} + +/* Custom scrollbars */ +div.cell:is( + .tag_output_scroll, + .tag_scroll-output, + .config_scroll_outputs + ) + div.cell_output::-webkit-scrollbar, +div.cell.tag_scroll-input div.cell_input::-webkit-scrollbar { + width: var(--mystnb-scrollbar-width); + height: var(--mystnb-scrollbar-height); +} + +div.cell:is( + .tag_output_scroll, + .tag_scroll-output, + .config_scroll_outputs + ) + div.cell_output::-webkit-scrollbar-thumb, +div.cell.tag_scroll-input div.cell_input::-webkit-scrollbar-thumb { + background: var(--mystnb-scrollbar-thumb-color); + border-radius: var(--mystnb-scrollbar-thumb-border-radius); +} + +div.cell:is( + .tag_output_scroll, + .tag_scroll-output, + .config_scroll_outputs + ) + div.cell_output::-webkit-scrollbar-thumb:hover, +div.cell.tag_scroll-input div.cell_input::-webkit-scrollbar-thumb:hover { + background: var(--mystnb-scrollbar-thumb-hover-color); +} + +/* In print mode, unset scroll styles */ +@media print { + div.cell:is( + .tag_output_scroll, + .tag_scroll-output, + .config_scroll_outputs + ) + div.cell_output, + div.cell.tag_scroll-input div.cell_input { + max-height: unset; + overflow-y: visible; + max-width: unset; + overflow-x: visible; + } +} + +/* Font colors for translated ANSI escape sequences +Color values are copied from Jupyter Notebook +https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21 +Background colors from +https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors +*/ +div.highlight .-Color-Bold { + font-weight: bold; +} + +div.highlight .-Color[class*=-Black] { + color: #3E424D +} + +div.highlight .-Color[class*=-Red] { + color: #E75C58 +} + +div.highlight .-Color[class*=-Green] { + color: #00A250 +} + +div.highlight .-Color[class*=-Yellow] { + color: #DDB62B +} + +div.highlight .-Color[class*=-Blue] { + color: #208FFB +} + +div.highlight .-Color[class*=-Magenta] { + color: #D160C4 +} + +div.highlight .-Color[class*=-Cyan] { + color: #60C6C8 +} + +div.highlight .-Color[class*=-White] { + color: #C5C1B4 +} + +div.highlight .-Color[class*=-BGBlack] { + background-color: #3E424D +} + +div.highlight .-Color[class*=-BGRed] { + background-color: #E75C58 +} + +div.highlight .-Color[class*=-BGGreen] { + background-color: #00A250 +} + +div.highlight .-Color[class*=-BGYellow] { + background-color: #DDB62B +} + +div.highlight .-Color[class*=-BGBlue] { + background-color: #208FFB +} + +div.highlight .-Color[class*=-BGMagenta] { + background-color: #D160C4 +} + +div.highlight .-Color[class*=-BGCyan] { + background-color: #60C6C8 +} + +div.highlight .-Color[class*=-BGWhite] { + background-color: #C5C1B4 +} + +/* Font colors for 8-bit ANSI */ + +div.highlight .-Color[class*=-C0] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC0] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C1] { + color: #800000 +} + +div.highlight .-Color[class*=-BGC1] { + background-color: #800000 +} + +div.highlight .-Color[class*=-C2] { + color: #008000 +} + +div.highlight .-Color[class*=-BGC2] { + background-color: #008000 +} + +div.highlight .-Color[class*=-C3] { + color: #808000 +} + +div.highlight .-Color[class*=-BGC3] { + background-color: #808000 +} + +div.highlight .-Color[class*=-C4] { + color: #000080 +} + +div.highlight .-Color[class*=-BGC4] { + background-color: #000080 +} + +div.highlight .-Color[class*=-C5] { + color: #800080 +} + +div.highlight .-Color[class*=-BGC5] { + background-color: #800080 +} + +div.highlight .-Color[class*=-C6] { + color: #008080 +} + +div.highlight .-Color[class*=-BGC6] { + background-color: #008080 +} + +div.highlight .-Color[class*=-C7] { + color: #C0C0C0 +} + +div.highlight .-Color[class*=-BGC7] { + background-color: #C0C0C0 +} + +div.highlight .-Color[class*=-C8] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC8] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C9] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC9] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C10] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC10] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C11] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC11] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C12] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC12] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C13] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC13] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C14] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC14] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C15] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC15] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C16] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC16] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C17] { + color: #00005F +} + +div.highlight .-Color[class*=-BGC17] { + background-color: #00005F +} + +div.highlight .-Color[class*=-C18] { + color: #000087 +} + +div.highlight .-Color[class*=-BGC18] { + background-color: #000087 +} + +div.highlight .-Color[class*=-C19] { + color: #0000AF +} + +div.highlight .-Color[class*=-BGC19] { + background-color: #0000AF +} + +div.highlight .-Color[class*=-C20] { + color: #0000D7 +} + +div.highlight .-Color[class*=-BGC20] { + background-color: #0000D7 +} + +div.highlight .-Color[class*=-C21] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC21] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C22] { + color: #005F00 +} + +div.highlight .-Color[class*=-BGC22] { + background-color: #005F00 +} + +div.highlight .-Color[class*=-C23] { + color: #005F5F +} + +div.highlight .-Color[class*=-BGC23] { + background-color: #005F5F +} + +div.highlight .-Color[class*=-C24] { + color: #005F87 +} + +div.highlight .-Color[class*=-BGC24] { + background-color: #005F87 +} + +div.highlight .-Color[class*=-C25] { + color: #005FAF +} + +div.highlight .-Color[class*=-BGC25] { + background-color: #005FAF +} + +div.highlight .-Color[class*=-C26] { + color: #005FD7 +} + +div.highlight .-Color[class*=-BGC26] { + background-color: #005FD7 +} + +div.highlight .-Color[class*=-C27] { + color: #005FFF +} + +div.highlight .-Color[class*=-BGC27] { + background-color: #005FFF +} + +div.highlight .-Color[class*=-C28] { + color: #008700 +} + +div.highlight .-Color[class*=-BGC28] { + background-color: #008700 +} + +div.highlight .-Color[class*=-C29] { + color: #00875F +} + +div.highlight .-Color[class*=-BGC29] { + background-color: #00875F +} + +div.highlight .-Color[class*=-C30] { + color: #008787 +} + +div.highlight .-Color[class*=-BGC30] { + background-color: #008787 +} + +div.highlight .-Color[class*=-C31] { + color: #0087AF +} + +div.highlight .-Color[class*=-BGC31] { + background-color: #0087AF +} + +div.highlight .-Color[class*=-C32] { + color: #0087D7 +} + +div.highlight .-Color[class*=-BGC32] { + background-color: #0087D7 +} + +div.highlight .-Color[class*=-C33] { + color: #0087FF +} + +div.highlight .-Color[class*=-BGC33] { + background-color: #0087FF +} + +div.highlight .-Color[class*=-C34] { + color: #00AF00 +} + +div.highlight .-Color[class*=-BGC34] { + background-color: #00AF00 +} + +div.highlight .-Color[class*=-C35] { + color: #00AF5F +} + +div.highlight .-Color[class*=-BGC35] { + background-color: #00AF5F +} + +div.highlight .-Color[class*=-C36] { + color: #00AF87 +} + +div.highlight .-Color[class*=-BGC36] { + background-color: #00AF87 +} + +div.highlight .-Color[class*=-C37] { + color: #00AFAF +} + +div.highlight .-Color[class*=-BGC37] { + background-color: #00AFAF +} + +div.highlight .-Color[class*=-C38] { + color: #00AFD7 +} + +div.highlight .-Color[class*=-BGC38] { + background-color: #00AFD7 +} + +div.highlight .-Color[class*=-C39] { + color: #00AFFF +} + +div.highlight .-Color[class*=-BGC39] { + background-color: #00AFFF +} + +div.highlight .-Color[class*=-C40] { + color: #00D700 +} + +div.highlight .-Color[class*=-BGC40] { + background-color: #00D700 +} + +div.highlight .-Color[class*=-C41] { + color: #00D75F +} + +div.highlight .-Color[class*=-BGC41] { + background-color: #00D75F +} + +div.highlight .-Color[class*=-C42] { + color: #00D787 +} + +div.highlight .-Color[class*=-BGC42] { + background-color: #00D787 +} + +div.highlight .-Color[class*=-C43] { + color: #00D7AF +} + +div.highlight .-Color[class*=-BGC43] { + background-color: #00D7AF +} + +div.highlight .-Color[class*=-C44] { + color: #00D7D7 +} + +div.highlight .-Color[class*=-BGC44] { + background-color: #00D7D7 +} + +div.highlight .-Color[class*=-C45] { + color: #00D7FF +} + +div.highlight .-Color[class*=-BGC45] { + background-color: #00D7FF +} + +div.highlight .-Color[class*=-C46] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC46] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C47] { + color: #00FF5F +} + +div.highlight .-Color[class*=-BGC47] { + background-color: #00FF5F +} + +div.highlight .-Color[class*=-C48] { + color: #00FF87 +} + +div.highlight .-Color[class*=-BGC48] { + background-color: #00FF87 +} + +div.highlight .-Color[class*=-C49] { + color: #00FFAF +} + +div.highlight .-Color[class*=-BGC49] { + background-color: #00FFAF +} + +div.highlight .-Color[class*=-C50] { + color: #00FFD7 +} + +div.highlight .-Color[class*=-BGC50] { + background-color: #00FFD7 +} + +div.highlight .-Color[class*=-C51] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC51] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C52] { + color: #5F0000 +} + +div.highlight .-Color[class*=-BGC52] { + background-color: #5F0000 +} + +div.highlight .-Color[class*=-C53] { + color: #5F005F +} + +div.highlight .-Color[class*=-BGC53] { + background-color: #5F005F +} + +div.highlight .-Color[class*=-C54] { + color: #5F0087 +} + +div.highlight .-Color[class*=-BGC54] { + background-color: #5F0087 +} + +div.highlight .-Color[class*=-C55] { + color: #5F00AF +} + +div.highlight .-Color[class*=-BGC55] { + background-color: #5F00AF +} + +div.highlight .-Color[class*=-C56] { + color: #5F00D7 +} + +div.highlight .-Color[class*=-BGC56] { + background-color: #5F00D7 +} + +div.highlight .-Color[class*=-C57] { + color: #5F00FF +} + +div.highlight .-Color[class*=-BGC57] { + background-color: #5F00FF +} + +div.highlight .-Color[class*=-C58] { + color: #5F5F00 +} + +div.highlight .-Color[class*=-BGC58] { + background-color: #5F5F00 +} + +div.highlight .-Color[class*=-C59] { + color: #5F5F5F +} + +div.highlight .-Color[class*=-BGC59] { + background-color: #5F5F5F +} + +div.highlight .-Color[class*=-C60] { + color: #5F5F87 +} + +div.highlight .-Color[class*=-BGC60] { + background-color: #5F5F87 +} + +div.highlight .-Color[class*=-C61] { + color: #5F5FAF +} + +div.highlight .-Color[class*=-BGC61] { + background-color: #5F5FAF +} + +div.highlight .-Color[class*=-C62] { + color: #5F5FD7 +} + +div.highlight .-Color[class*=-BGC62] { + background-color: #5F5FD7 +} + +div.highlight .-Color[class*=-C63] { + color: #5F5FFF +} + +div.highlight .-Color[class*=-BGC63] { + background-color: #5F5FFF +} + +div.highlight .-Color[class*=-C64] { + color: #5F8700 +} + +div.highlight .-Color[class*=-BGC64] { + background-color: #5F8700 +} + +div.highlight .-Color[class*=-C65] { + color: #5F875F +} + +div.highlight .-Color[class*=-BGC65] { + background-color: #5F875F +} + +div.highlight .-Color[class*=-C66] { + color: #5F8787 +} + +div.highlight .-Color[class*=-BGC66] { + background-color: #5F8787 +} + +div.highlight .-Color[class*=-C67] { + color: #5F87AF +} + +div.highlight .-Color[class*=-BGC67] { + background-color: #5F87AF +} + +div.highlight .-Color[class*=-C68] { + color: #5F87D7 +} + +div.highlight .-Color[class*=-BGC68] { + background-color: #5F87D7 +} + +div.highlight .-Color[class*=-C69] { + color: #5F87FF +} + +div.highlight .-Color[class*=-BGC69] { + background-color: #5F87FF +} + +div.highlight .-Color[class*=-C70] { + color: #5FAF00 +} + +div.highlight .-Color[class*=-BGC70] { + background-color: #5FAF00 +} + +div.highlight .-Color[class*=-C71] { + color: #5FAF5F +} + +div.highlight .-Color[class*=-BGC71] { + background-color: #5FAF5F +} + +div.highlight .-Color[class*=-C72] { + color: #5FAF87 +} + +div.highlight .-Color[class*=-BGC72] { + background-color: #5FAF87 +} + +div.highlight .-Color[class*=-C73] { + color: #5FAFAF +} + +div.highlight .-Color[class*=-BGC73] { + background-color: #5FAFAF +} + +div.highlight .-Color[class*=-C74] { + color: #5FAFD7 +} + +div.highlight .-Color[class*=-BGC74] { + background-color: #5FAFD7 +} + +div.highlight .-Color[class*=-C75] { + color: #5FAFFF +} + +div.highlight .-Color[class*=-BGC75] { + background-color: #5FAFFF +} + +div.highlight .-Color[class*=-C76] { + color: #5FD700 +} + +div.highlight .-Color[class*=-BGC76] { + background-color: #5FD700 +} + +div.highlight .-Color[class*=-C77] { + color: #5FD75F +} + +div.highlight .-Color[class*=-BGC77] { + background-color: #5FD75F +} + +div.highlight .-Color[class*=-C78] { + color: #5FD787 +} + +div.highlight .-Color[class*=-BGC78] { + background-color: #5FD787 +} + +div.highlight .-Color[class*=-C79] { + color: #5FD7AF +} + +div.highlight .-Color[class*=-BGC79] { + background-color: #5FD7AF +} + +div.highlight .-Color[class*=-C80] { + color: #5FD7D7 +} + +div.highlight .-Color[class*=-BGC80] { + background-color: #5FD7D7 +} + +div.highlight .-Color[class*=-C81] { + color: #5FD7FF +} + +div.highlight .-Color[class*=-BGC81] { + background-color: #5FD7FF +} + +div.highlight .-Color[class*=-C82] { + color: #5FFF00 +} + +div.highlight .-Color[class*=-BGC82] { + background-color: #5FFF00 +} + +div.highlight .-Color[class*=-C83] { + color: #5FFF5F +} + +div.highlight .-Color[class*=-BGC83] { + background-color: #5FFF5F +} + +div.highlight .-Color[class*=-C84] { + color: #5FFF87 +} + +div.highlight .-Color[class*=-BGC84] { + background-color: #5FFF87 +} + +div.highlight .-Color[class*=-C85] { + color: #5FFFAF +} + +div.highlight .-Color[class*=-BGC85] { + background-color: #5FFFAF +} + +div.highlight .-Color[class*=-C86] { + color: #5FFFD7 +} + +div.highlight .-Color[class*=-BGC86] { + background-color: #5FFFD7 +} + +div.highlight .-Color[class*=-C87] { + color: #5FFFFF +} + +div.highlight .-Color[class*=-BGC87] { + background-color: #5FFFFF +} + +div.highlight .-Color[class*=-C88] { + color: #870000 +} + +div.highlight .-Color[class*=-BGC88] { + background-color: #870000 +} + +div.highlight .-Color[class*=-C89] { + color: #87005F +} + +div.highlight .-Color[class*=-BGC89] { + background-color: #87005F +} + +div.highlight .-Color[class*=-C90] { + color: #870087 +} + +div.highlight .-Color[class*=-BGC90] { + background-color: #870087 +} + +div.highlight .-Color[class*=-C91] { + color: #8700AF +} + +div.highlight .-Color[class*=-BGC91] { + background-color: #8700AF +} + +div.highlight .-Color[class*=-C92] { + color: #8700D7 +} + +div.highlight .-Color[class*=-BGC92] { + background-color: #8700D7 +} + +div.highlight .-Color[class*=-C93] { + color: #8700FF +} + +div.highlight .-Color[class*=-BGC93] { + background-color: #8700FF +} + +div.highlight .-Color[class*=-C94] { + color: #875F00 +} + +div.highlight .-Color[class*=-BGC94] { + background-color: #875F00 +} + +div.highlight .-Color[class*=-C95] { + color: #875F5F +} + +div.highlight .-Color[class*=-BGC95] { + background-color: #875F5F +} + +div.highlight .-Color[class*=-C96] { + color: #875F87 +} + +div.highlight .-Color[class*=-BGC96] { + background-color: #875F87 +} + +div.highlight .-Color[class*=-C97] { + color: #875FAF +} + +div.highlight .-Color[class*=-BGC97] { + background-color: #875FAF +} + +div.highlight .-Color[class*=-C98] { + color: #875FD7 +} + +div.highlight .-Color[class*=-BGC98] { + background-color: #875FD7 +} + +div.highlight .-Color[class*=-C99] { + color: #875FFF +} + +div.highlight .-Color[class*=-BGC99] { + background-color: #875FFF +} + +div.highlight .-Color[class*=-C100] { + color: #878700 +} + +div.highlight .-Color[class*=-BGC100] { + background-color: #878700 +} + +div.highlight .-Color[class*=-C101] { + color: #87875F +} + +div.highlight .-Color[class*=-BGC101] { + background-color: #87875F +} + +div.highlight .-Color[class*=-C102] { + color: #878787 +} + +div.highlight .-Color[class*=-BGC102] { + background-color: #878787 +} + +div.highlight .-Color[class*=-C103] { + color: #8787AF +} + +div.highlight .-Color[class*=-BGC103] { + background-color: #8787AF +} + +div.highlight .-Color[class*=-C104] { + color: #8787D7 +} + +div.highlight .-Color[class*=-BGC104] { + background-color: #8787D7 +} + +div.highlight .-Color[class*=-C105] { + color: #8787FF +} + +div.highlight .-Color[class*=-BGC105] { + background-color: #8787FF +} + +div.highlight .-Color[class*=-C106] { + color: #87AF00 +} + +div.highlight .-Color[class*=-BGC106] { + background-color: #87AF00 +} + +div.highlight .-Color[class*=-C107] { + color: #87AF5F +} + +div.highlight .-Color[class*=-BGC107] { + background-color: #87AF5F +} + +div.highlight .-Color[class*=-C108] { + color: #87AF87 +} + +div.highlight .-Color[class*=-BGC108] { + background-color: #87AF87 +} + +div.highlight .-Color[class*=-C109] { + color: #87AFAF +} + +div.highlight .-Color[class*=-BGC109] { + background-color: #87AFAF +} + +div.highlight .-Color[class*=-C110] { + color: #87AFD7 +} + +div.highlight .-Color[class*=-BGC110] { + background-color: #87AFD7 +} + +div.highlight .-Color[class*=-C111] { + color: #87AFFF +} + +div.highlight .-Color[class*=-BGC111] { + background-color: #87AFFF +} + +div.highlight .-Color[class*=-C112] { + color: #87D700 +} + +div.highlight .-Color[class*=-BGC112] { + background-color: #87D700 +} + +div.highlight .-Color[class*=-C113] { + color: #87D75F +} + +div.highlight .-Color[class*=-BGC113] { + background-color: #87D75F +} + +div.highlight .-Color[class*=-C114] { + color: #87D787 +} + +div.highlight .-Color[class*=-BGC114] { + background-color: #87D787 +} + +div.highlight .-Color[class*=-C115] { + color: #87D7AF +} + +div.highlight .-Color[class*=-BGC115] { + background-color: #87D7AF +} + +div.highlight .-Color[class*=-C116] { + color: #87D7D7 +} + +div.highlight .-Color[class*=-BGC116] { + background-color: #87D7D7 +} + +div.highlight .-Color[class*=-C117] { + color: #87D7FF +} + +div.highlight .-Color[class*=-BGC117] { + background-color: #87D7FF +} + +div.highlight .-Color[class*=-C118] { + color: #87FF00 +} + +div.highlight .-Color[class*=-BGC118] { + background-color: #87FF00 +} + +div.highlight .-Color[class*=-C119] { + color: #87FF5F +} + +div.highlight .-Color[class*=-BGC119] { + background-color: #87FF5F +} + +div.highlight .-Color[class*=-C120] { + color: #87FF87 +} + +div.highlight .-Color[class*=-BGC120] { + background-color: #87FF87 +} + +div.highlight .-Color[class*=-C121] { + color: #87FFAF +} + +div.highlight .-Color[class*=-BGC121] { + background-color: #87FFAF +} + +div.highlight .-Color[class*=-C122] { + color: #87FFD7 +} + +div.highlight .-Color[class*=-BGC122] { + background-color: #87FFD7 +} + +div.highlight .-Color[class*=-C123] { + color: #87FFFF +} + +div.highlight .-Color[class*=-BGC123] { + background-color: #87FFFF +} + +div.highlight .-Color[class*=-C124] { + color: #AF0000 +} + +div.highlight .-Color[class*=-BGC124] { + background-color: #AF0000 +} + +div.highlight .-Color[class*=-C125] { + color: #AF005F +} + +div.highlight .-Color[class*=-BGC125] { + background-color: #AF005F +} + +div.highlight .-Color[class*=-C126] { + color: #AF0087 +} + +div.highlight .-Color[class*=-BGC126] { + background-color: #AF0087 +} + +div.highlight .-Color[class*=-C127] { + color: #AF00AF +} + +div.highlight .-Color[class*=-BGC127] { + background-color: #AF00AF +} + +div.highlight .-Color[class*=-C128] { + color: #AF00D7 +} + +div.highlight .-Color[class*=-BGC128] { + background-color: #AF00D7 +} + +div.highlight .-Color[class*=-C129] { + color: #AF00FF +} + +div.highlight .-Color[class*=-BGC129] { + background-color: #AF00FF +} + +div.highlight .-Color[class*=-C130] { + color: #AF5F00 +} + +div.highlight .-Color[class*=-BGC130] { + background-color: #AF5F00 +} + +div.highlight .-Color[class*=-C131] { + color: #AF5F5F +} + +div.highlight .-Color[class*=-BGC131] { + background-color: #AF5F5F +} + +div.highlight .-Color[class*=-C132] { + color: #AF5F87 +} + +div.highlight .-Color[class*=-BGC132] { + background-color: #AF5F87 +} + +div.highlight .-Color[class*=-C133] { + color: #AF5FAF +} + +div.highlight .-Color[class*=-BGC133] { + background-color: #AF5FAF +} + +div.highlight .-Color[class*=-C134] { + color: #AF5FD7 +} + +div.highlight .-Color[class*=-BGC134] { + background-color: #AF5FD7 +} + +div.highlight .-Color[class*=-C135] { + color: #AF5FFF +} + +div.highlight .-Color[class*=-BGC135] { + background-color: #AF5FFF +} + +div.highlight .-Color[class*=-C136] { + color: #AF8700 +} + +div.highlight .-Color[class*=-BGC136] { + background-color: #AF8700 +} + +div.highlight .-Color[class*=-C137] { + color: #AF875F +} + +div.highlight .-Color[class*=-BGC137] { + background-color: #AF875F +} + +div.highlight .-Color[class*=-C138] { + color: #AF8787 +} + +div.highlight .-Color[class*=-BGC138] { + background-color: #AF8787 +} + +div.highlight .-Color[class*=-C139] { + color: #AF87AF +} + +div.highlight .-Color[class*=-BGC139] { + background-color: #AF87AF +} + +div.highlight .-Color[class*=-C140] { + color: #AF87D7 +} + +div.highlight .-Color[class*=-BGC140] { + background-color: #AF87D7 +} + +div.highlight .-Color[class*=-C141] { + color: #AF87FF +} + +div.highlight .-Color[class*=-BGC141] { + background-color: #AF87FF +} + +div.highlight .-Color[class*=-C142] { + color: #AFAF00 +} + +div.highlight .-Color[class*=-BGC142] { + background-color: #AFAF00 +} + +div.highlight .-Color[class*=-C143] { + color: #AFAF5F +} + +div.highlight .-Color[class*=-BGC143] { + background-color: #AFAF5F +} + +div.highlight .-Color[class*=-C144] { + color: #AFAF87 +} + +div.highlight .-Color[class*=-BGC144] { + background-color: #AFAF87 +} + +div.highlight .-Color[class*=-C145] { + color: #AFAFAF +} + +div.highlight .-Color[class*=-BGC145] { + background-color: #AFAFAF +} + +div.highlight .-Color[class*=-C146] { + color: #AFAFD7 +} + +div.highlight .-Color[class*=-BGC146] { + background-color: #AFAFD7 +} + +div.highlight .-Color[class*=-C147] { + color: #AFAFFF +} + +div.highlight .-Color[class*=-BGC147] { + background-color: #AFAFFF +} + +div.highlight .-Color[class*=-C148] { + color: #AFD700 +} + +div.highlight .-Color[class*=-BGC148] { + background-color: #AFD700 +} + +div.highlight .-Color[class*=-C149] { + color: #AFD75F +} + +div.highlight .-Color[class*=-BGC149] { + background-color: #AFD75F +} + +div.highlight .-Color[class*=-C150] { + color: #AFD787 +} + +div.highlight .-Color[class*=-BGC150] { + background-color: #AFD787 +} + +div.highlight .-Color[class*=-C151] { + color: #AFD7AF +} + +div.highlight .-Color[class*=-BGC151] { + background-color: #AFD7AF +} + +div.highlight .-Color[class*=-C152] { + color: #AFD7D7 +} + +div.highlight .-Color[class*=-BGC152] { + background-color: #AFD7D7 +} + +div.highlight .-Color[class*=-C153] { + color: #AFD7FF +} + +div.highlight .-Color[class*=-BGC153] { + background-color: #AFD7FF +} + +div.highlight .-Color[class*=-C154] { + color: #AFFF00 +} + +div.highlight .-Color[class*=-BGC154] { + background-color: #AFFF00 +} + +div.highlight .-Color[class*=-C155] { + color: #AFFF5F +} + +div.highlight .-Color[class*=-BGC155] { + background-color: #AFFF5F +} + +div.highlight .-Color[class*=-C156] { + color: #AFFF87 +} + +div.highlight .-Color[class*=-BGC156] { + background-color: #AFFF87 +} + +div.highlight .-Color[class*=-C157] { + color: #AFFFAF +} + +div.highlight .-Color[class*=-BGC157] { + background-color: #AFFFAF +} + +div.highlight .-Color[class*=-C158] { + color: #AFFFD7 +} + +div.highlight .-Color[class*=-BGC158] { + background-color: #AFFFD7 +} + +div.highlight .-Color[class*=-C159] { + color: #AFFFFF +} + +div.highlight .-Color[class*=-BGC159] { + background-color: #AFFFFF +} + +div.highlight .-Color[class*=-C160] { + color: #D70000 +} + +div.highlight .-Color[class*=-BGC160] { + background-color: #D70000 +} + +div.highlight .-Color[class*=-C161] { + color: #D7005F +} + +div.highlight .-Color[class*=-BGC161] { + background-color: #D7005F +} + +div.highlight .-Color[class*=-C162] { + color: #D70087 +} + +div.highlight .-Color[class*=-BGC162] { + background-color: #D70087 +} + +div.highlight .-Color[class*=-C163] { + color: #D700AF +} + +div.highlight .-Color[class*=-BGC163] { + background-color: #D700AF +} + +div.highlight .-Color[class*=-C164] { + color: #D700D7 +} + +div.highlight .-Color[class*=-BGC164] { + background-color: #D700D7 +} + +div.highlight .-Color[class*=-C165] { + color: #D700FF +} + +div.highlight .-Color[class*=-BGC165] { + background-color: #D700FF +} + +div.highlight .-Color[class*=-C166] { + color: #D75F00 +} + +div.highlight .-Color[class*=-BGC166] { + background-color: #D75F00 +} + +div.highlight .-Color[class*=-C167] { + color: #D75F5F +} + +div.highlight .-Color[class*=-BGC167] { + background-color: #D75F5F +} + +div.highlight .-Color[class*=-C168] { + color: #D75F87 +} + +div.highlight .-Color[class*=-BGC168] { + background-color: #D75F87 +} + +div.highlight .-Color[class*=-C169] { + color: #D75FAF +} + +div.highlight .-Color[class*=-BGC169] { + background-color: #D75FAF +} + +div.highlight .-Color[class*=-C170] { + color: #D75FD7 +} + +div.highlight .-Color[class*=-BGC170] { + background-color: #D75FD7 +} + +div.highlight .-Color[class*=-C171] { + color: #D75FFF +} + +div.highlight .-Color[class*=-BGC171] { + background-color: #D75FFF +} + +div.highlight .-Color[class*=-C172] { + color: #D78700 +} + +div.highlight .-Color[class*=-BGC172] { + background-color: #D78700 +} + +div.highlight .-Color[class*=-C173] { + color: #D7875F +} + +div.highlight .-Color[class*=-BGC173] { + background-color: #D7875F +} + +div.highlight .-Color[class*=-C174] { + color: #D78787 +} + +div.highlight .-Color[class*=-BGC174] { + background-color: #D78787 +} + +div.highlight .-Color[class*=-C175] { + color: #D787AF +} + +div.highlight .-Color[class*=-BGC175] { + background-color: #D787AF +} + +div.highlight .-Color[class*=-C176] { + color: #D787D7 +} + +div.highlight .-Color[class*=-BGC176] { + background-color: #D787D7 +} + +div.highlight .-Color[class*=-C177] { + color: #D787FF +} + +div.highlight .-Color[class*=-BGC177] { + background-color: #D787FF +} + +div.highlight .-Color[class*=-C178] { + color: #D7AF00 +} + +div.highlight .-Color[class*=-BGC178] { + background-color: #D7AF00 +} + +div.highlight .-Color[class*=-C179] { + color: #D7AF5F +} + +div.highlight .-Color[class*=-BGC179] { + background-color: #D7AF5F +} + +div.highlight .-Color[class*=-C180] { + color: #D7AF87 +} + +div.highlight .-Color[class*=-BGC180] { + background-color: #D7AF87 +} + +div.highlight .-Color[class*=-C181] { + color: #D7AFAF +} + +div.highlight .-Color[class*=-BGC181] { + background-color: #D7AFAF +} + +div.highlight .-Color[class*=-C182] { + color: #D7AFD7 +} + +div.highlight .-Color[class*=-BGC182] { + background-color: #D7AFD7 +} + +div.highlight .-Color[class*=-C183] { + color: #D7AFFF +} + +div.highlight .-Color[class*=-BGC183] { + background-color: #D7AFFF +} + +div.highlight .-Color[class*=-C184] { + color: #D7D700 +} + +div.highlight .-Color[class*=-BGC184] { + background-color: #D7D700 +} + +div.highlight .-Color[class*=-C185] { + color: #D7D75F +} + +div.highlight .-Color[class*=-BGC185] { + background-color: #D7D75F +} + +div.highlight .-Color[class*=-C186] { + color: #D7D787 +} + +div.highlight .-Color[class*=-BGC186] { + background-color: #D7D787 +} + +div.highlight .-Color[class*=-C187] { + color: #D7D7AF +} + +div.highlight .-Color[class*=-BGC187] { + background-color: #D7D7AF +} + +div.highlight .-Color[class*=-C188] { + color: #D7D7D7 +} + +div.highlight .-Color[class*=-BGC188] { + background-color: #D7D7D7 +} + +div.highlight .-Color[class*=-C189] { + color: #D7D7FF +} + +div.highlight .-Color[class*=-BGC189] { + background-color: #D7D7FF +} + +div.highlight .-Color[class*=-C190] { + color: #D7FF00 +} + +div.highlight .-Color[class*=-BGC190] { + background-color: #D7FF00 +} + +div.highlight .-Color[class*=-C191] { + color: #D7FF5F +} + +div.highlight .-Color[class*=-BGC191] { + background-color: #D7FF5F +} + +div.highlight .-Color[class*=-C192] { + color: #D7FF87 +} + +div.highlight .-Color[class*=-BGC192] { + background-color: #D7FF87 +} + +div.highlight .-Color[class*=-C193] { + color: #D7FFAF +} + +div.highlight .-Color[class*=-BGC193] { + background-color: #D7FFAF +} + +div.highlight .-Color[class*=-C194] { + color: #D7FFD7 +} + +div.highlight .-Color[class*=-BGC194] { + background-color: #D7FFD7 +} + +div.highlight .-Color[class*=-C195] { + color: #D7FFFF +} + +div.highlight .-Color[class*=-BGC195] { + background-color: #D7FFFF +} + +div.highlight .-Color[class*=-C196] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC196] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C197] { + color: #FF005F +} + +div.highlight .-Color[class*=-BGC197] { + background-color: #FF005F +} + +div.highlight .-Color[class*=-C198] { + color: #FF0087 +} + +div.highlight .-Color[class*=-BGC198] { + background-color: #FF0087 +} + +div.highlight .-Color[class*=-C199] { + color: #FF00AF +} + +div.highlight .-Color[class*=-BGC199] { + background-color: #FF00AF +} + +div.highlight .-Color[class*=-C200] { + color: #FF00D7 +} + +div.highlight .-Color[class*=-BGC200] { + background-color: #FF00D7 +} + +div.highlight .-Color[class*=-C201] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC201] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C202] { + color: #FF5F00 +} + +div.highlight .-Color[class*=-BGC202] { + background-color: #FF5F00 +} + +div.highlight .-Color[class*=-C203] { + color: #FF5F5F +} + +div.highlight .-Color[class*=-BGC203] { + background-color: #FF5F5F +} + +div.highlight .-Color[class*=-C204] { + color: #FF5F87 +} + +div.highlight .-Color[class*=-BGC204] { + background-color: #FF5F87 +} + +div.highlight .-Color[class*=-C205] { + color: #FF5FAF +} + +div.highlight .-Color[class*=-BGC205] { + background-color: #FF5FAF +} + +div.highlight .-Color[class*=-C206] { + color: #FF5FD7 +} + +div.highlight .-Color[class*=-BGC206] { + background-color: #FF5FD7 +} + +div.highlight .-Color[class*=-C207] { + color: #FF5FFF +} + +div.highlight .-Color[class*=-BGC207] { + background-color: #FF5FFF +} + +div.highlight .-Color[class*=-C208] { + color: #FF8700 +} + +div.highlight .-Color[class*=-BGC208] { + background-color: #FF8700 +} + +div.highlight .-Color[class*=-C209] { + color: #FF875F +} + +div.highlight .-Color[class*=-BGC209] { + background-color: #FF875F +} + +div.highlight .-Color[class*=-C210] { + color: #FF8787 +} + +div.highlight .-Color[class*=-BGC210] { + background-color: #FF8787 +} + +div.highlight .-Color[class*=-C211] { + color: #FF87AF +} + +div.highlight .-Color[class*=-BGC211] { + background-color: #FF87AF +} + +div.highlight .-Color[class*=-C212] { + color: #FF87D7 +} + +div.highlight .-Color[class*=-BGC212] { + background-color: #FF87D7 +} + +div.highlight .-Color[class*=-C213] { + color: #FF87FF +} + +div.highlight .-Color[class*=-BGC213] { + background-color: #FF87FF +} + +div.highlight .-Color[class*=-C214] { + color: #FFAF00 +} + +div.highlight .-Color[class*=-BGC214] { + background-color: #FFAF00 +} + +div.highlight .-Color[class*=-C215] { + color: #FFAF5F +} + +div.highlight .-Color[class*=-BGC215] { + background-color: #FFAF5F +} + +div.highlight .-Color[class*=-C216] { + color: #FFAF87 +} + +div.highlight .-Color[class*=-BGC216] { + background-color: #FFAF87 +} + +div.highlight .-Color[class*=-C217] { + color: #FFAFAF +} + +div.highlight .-Color[class*=-BGC217] { + background-color: #FFAFAF +} + +div.highlight .-Color[class*=-C218] { + color: #FFAFD7 +} + +div.highlight .-Color[class*=-BGC218] { + background-color: #FFAFD7 +} + +div.highlight .-Color[class*=-C219] { + color: #FFAFFF +} + +div.highlight .-Color[class*=-BGC219] { + background-color: #FFAFFF +} + +div.highlight .-Color[class*=-C220] { + color: #FFD700 +} + +div.highlight .-Color[class*=-BGC220] { + background-color: #FFD700 +} + +div.highlight .-Color[class*=-C221] { + color: #FFD75F +} + +div.highlight .-Color[class*=-BGC221] { + background-color: #FFD75F +} + +div.highlight .-Color[class*=-C222] { + color: #FFD787 +} + +div.highlight .-Color[class*=-BGC222] { + background-color: #FFD787 +} + +div.highlight .-Color[class*=-C223] { + color: #FFD7AF +} + +div.highlight .-Color[class*=-BGC223] { + background-color: #FFD7AF +} + +div.highlight .-Color[class*=-C224] { + color: #FFD7D7 +} + +div.highlight .-Color[class*=-BGC224] { + background-color: #FFD7D7 +} + +div.highlight .-Color[class*=-C225] { + color: #FFD7FF +} + +div.highlight .-Color[class*=-BGC225] { + background-color: #FFD7FF +} + +div.highlight .-Color[class*=-C226] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC226] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C227] { + color: #FFFF5F +} + +div.highlight .-Color[class*=-BGC227] { + background-color: #FFFF5F +} + +div.highlight .-Color[class*=-C228] { + color: #FFFF87 +} + +div.highlight .-Color[class*=-BGC228] { + background-color: #FFFF87 +} + +div.highlight .-Color[class*=-C229] { + color: #FFFFAF +} + +div.highlight .-Color[class*=-BGC229] { + background-color: #FFFFAF +} + +div.highlight .-Color[class*=-C230] { + color: #FFFFD7 +} + +div.highlight .-Color[class*=-BGC230] { + background-color: #FFFFD7 +} + +div.highlight .-Color[class*=-C231] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC231] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C232] { + color: #080808 +} + +div.highlight .-Color[class*=-BGC232] { + background-color: #080808 +} + +div.highlight .-Color[class*=-C233] { + color: #121212 +} + +div.highlight .-Color[class*=-BGC233] { + background-color: #121212 +} + +div.highlight .-Color[class*=-C234] { + color: #1C1C1C +} + +div.highlight .-Color[class*=-BGC234] { + background-color: #1C1C1C +} + +div.highlight .-Color[class*=-C235] { + color: #262626 +} + +div.highlight .-Color[class*=-BGC235] { + background-color: #262626 +} + +div.highlight .-Color[class*=-C236] { + color: #303030 +} + +div.highlight .-Color[class*=-BGC236] { + background-color: #303030 +} + +div.highlight .-Color[class*=-C237] { + color: #3A3A3A +} + +div.highlight .-Color[class*=-BGC237] { + background-color: #3A3A3A +} + +div.highlight .-Color[class*=-C238] { + color: #444444 +} + +div.highlight .-Color[class*=-BGC238] { + background-color: #444444 +} + +div.highlight .-Color[class*=-C239] { + color: #4E4E4E +} + +div.highlight .-Color[class*=-BGC239] { + background-color: #4E4E4E +} + +div.highlight .-Color[class*=-C240] { + color: #585858 +} + +div.highlight .-Color[class*=-BGC240] { + background-color: #585858 +} + +div.highlight .-Color[class*=-C241] { + color: #626262 +} + +div.highlight .-Color[class*=-BGC241] { + background-color: #626262 +} + +div.highlight .-Color[class*=-C242] { + color: #6C6C6C +} + +div.highlight .-Color[class*=-BGC242] { + background-color: #6C6C6C +} + +div.highlight .-Color[class*=-C243] { + color: #767676 +} + +div.highlight .-Color[class*=-BGC243] { + background-color: #767676 +} + +div.highlight .-Color[class*=-C244] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC244] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C245] { + color: #8A8A8A +} + +div.highlight .-Color[class*=-BGC245] { + background-color: #8A8A8A +} + +div.highlight .-Color[class*=-C246] { + color: #949494 +} + +div.highlight .-Color[class*=-BGC246] { + background-color: #949494 +} + +div.highlight .-Color[class*=-C247] { + color: #9E9E9E +} + +div.highlight .-Color[class*=-BGC247] { + background-color: #9E9E9E +} + +div.highlight .-Color[class*=-C248] { + color: #A8A8A8 +} + +div.highlight .-Color[class*=-BGC248] { + background-color: #A8A8A8 +} + +div.highlight .-Color[class*=-C249] { + color: #B2B2B2 +} + +div.highlight .-Color[class*=-BGC249] { + background-color: #B2B2B2 +} + +div.highlight .-Color[class*=-C250] { + color: #BCBCBC +} + +div.highlight .-Color[class*=-BGC250] { + background-color: #BCBCBC +} + +div.highlight .-Color[class*=-C251] { + color: #C6C6C6 +} + +div.highlight .-Color[class*=-BGC251] { + background-color: #C6C6C6 +} + +div.highlight .-Color[class*=-C252] { + color: #D0D0D0 +} + +div.highlight .-Color[class*=-BGC252] { + background-color: #D0D0D0 +} + +div.highlight .-Color[class*=-C253] { + color: #DADADA +} + +div.highlight .-Color[class*=-BGC253] { + background-color: #DADADA +} + +div.highlight .-Color[class*=-C254] { + color: #E4E4E4 +} + +div.highlight .-Color[class*=-BGC254] { + background-color: #E4E4E4 +} + +div.highlight .-Color[class*=-C255] { + color: #EEEEEE +} + +div.highlight .-Color[class*=-BGC255] { + background-color: #EEEEEE +} diff --git a/functions/master/aggregate/1.3.0/static/aggregate.html b/functions/master/aggregate/1.3.0/static/aggregate.html index dd189991..94dd4dee 100644 --- a/functions/master/aggregate/1.3.0/static/aggregate.html +++ b/functions/master/aggregate/1.3.0/static/aggregate.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/aggregate/1.3.0/static/documentation.html b/functions/master/aggregate/1.3.0/static/documentation.html index 6f0be75b..5aac022e 100644 --- a/functions/master/aggregate/1.3.0/static/documentation.html +++ b/functions/master/aggregate/1.3.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/aggregate/1.3.0/static/example.html b/functions/master/aggregate/1.3.0/static/example.html index 0f230b87..d1ba3b94 100644 --- a/functions/master/aggregate/1.3.0/static/example.html +++ b/functions/master/aggregate/1.3.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/aggregate/latest/static/aggregate.html b/functions/master/aggregate/latest/static/aggregate.html index dd189991..94dd4dee 100644 --- a/functions/master/aggregate/latest/static/aggregate.html +++ b/functions/master/aggregate/latest/static/aggregate.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/aggregate/latest/static/documentation.html b/functions/master/aggregate/latest/static/documentation.html index 6f0be75b..5aac022e 100644 --- a/functions/master/aggregate/latest/static/documentation.html +++ b/functions/master/aggregate/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/aggregate/latest/static/example.html b/functions/master/aggregate/latest/static/example.html index 0f230b87..d1ba3b94 100644 --- a/functions/master/aggregate/latest/static/example.html +++ b/functions/master/aggregate/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/arc_to_parquet/1.5.0/src/README.md b/functions/master/arc_to_parquet/1.5.0/src/README.md new file mode 100644 index 00000000..568ea4e4 --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/README.md @@ -0,0 +1,28 @@ +## arc_to_parquet + +Retrieve a remote archive and save locally as a parquet file, [source](arc_to_parquet.py) + +Usage example: + +```python +import mlrun, os +mlrun.mlconf.dbpath = 'http://mlrun-api:8080' +mlrun.mlconf.hub_url = '/User/functions/{name}/function.yaml' + +# load arc_to_parquet function from Github +func = mlrun.import_function("hub://arc_to_parquet").apply(mlrun.mount_v3io()) + +# create and run the task +archive = "https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz" + +arc_to_parq_task = mlrun.NewTask(name='tasks - acquire remote', + params={'archive_url': archive, + 'key' : 'HIGGS'}) +# run +run = func.run(arc_to_parq_task, artifact_path='/User/artifacts') +``` + +Output: + +``` +``` \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.ipynb b/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.ipynb new file mode 100644 index 00000000..59d2cefb --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.ipynb @@ -0,0 +1,834 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Archive to parquet function Example\n", + "> the arc_to_parquet function is typically for large files, the function accept an input of archive and stores the data into a file system.\n", + "in the example we will use arc_to_parquet function to unarchive the higgs-sample data-file stored on s3,\n", + "and will store it on the local file system in parquet format , " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# upload environment variables from env file if exists\n", + "import os,mlrun\n", + " \n", + "# Specify path\n", + "path = \"/tmp/examples_ci.env\"\n", + " \n", + "if os.path.exists(path):\n", + " env_dict = mlrun.set_env_from_file(path, return_dict=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-12-25 11:14:04,646 [info] loaded project arch-to-parquet-example from MLRun DB\n" + ] + } + ], + "source": [ + "# create the new project\n", + "project_name = 'arch-to-parquet-example'\n", + "\n", + "# Initialize the MLRun project object\n", + "project = mlrun.get_or_create_project(project_name, context=\"./\", user_project=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# import packages\n", + "import mlrun\n", + "from mlrun import import_function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# declare the dataset\n", + "DATA_URL = \"https://s3.wasabisys.com/iguazio/data/market-palce/arc_to_parquet/higgs-sample.csv.gz\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# import the function\n", + "arc_to_parquet_function = import_function(\"hub://arc_to_parquet\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-12-25 11:14:05,030 [warning] it is recommended to use k8s secret (specify secret_name), specifying the aws_access_key/aws_secret_key directly is unsafe\n", + "> 2022-12-25 11:14:05,046 [info] starting run arc-to-parquet-arc_to_parquet uid=cb1962a5333f4f9f9c16faabfd1e94c1 DB=http://mlrun-api:8080\n", + "> 2022-12-25 11:14:05,203 [info] Job is running in the background, pod: arc-to-parquet-arc-to-parquet-8kz4b\n", + "> 2022-12-25 11:14:44,126 [info] downloading https://s3.wasabisys.com/iguazio/data/market-palce/arc_to_parquet/higgs-sample.csv.gz to local temp file\n", + "> 2022-12-25 11:14:44,793 [info] destination file does not exist, downloading\n", + "> 2022-12-25 11:14:45,143 [info] To track results use the CLI: {'info_cmd': 'mlrun get run cb1962a5333f4f9f9c16faabfd1e94c1 -p arch-to-parquet-example-jovyan', 'logs_cmd': 'mlrun logs cb1962a5333f4f9f9c16faabfd1e94c1 -p arch-to-parquet-example-jovyan'}\n", + "> 2022-12-25 11:14:45,144 [info] run executed, status=completed\n", + "final state: completed\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
arch-to-parquet-example-jovyan0Dec 25 11:14:44completedarc-to-parquet-arc_to_parquet
kind=job
owner=jovyan
mlrun/client_version=1.2.1-rc7
host=arc-to-parquet-arc-to-parquet-8kz4b
archive_url
key=higgs-sample
higgs-sample
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-12-25 11:14:47,549 [info] run executed, status=completed\n" + ] + } + ], + "source": [ + "# run the function\n", + "arc_to_parquet_run = arc_to_parquet_function.run(params={\"key\": \"higgs-sample\"},\n", + " handler=\"arc_to_parquet\",\n", + " inputs={\"archive_url\": DATA_URL}\n", + " )\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Show the results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Unnamed: 01.000000000000000000e+008.692932128906250000e-01-6.350818276405334473e-012.256902605295181274e-013.274700641632080078e-01-6.899932026863098145e-017.542022466659545898e-01-2.485731393098831177e-01-1.092063903808593750e+00...-1.045456994324922562e-02-4.576716944575309753e-023.101961374282836914e+001.353760004043579102e+009.795631170272827148e-019.780761599540710449e-019.200048446655273438e-017.216574549674987793e-019.887509346008300781e-018.766783475875854492e-01
001.00.9075420.3291470.3594121.497970-0.3130101.095531-0.557525-1.588230...-1.138930-0.0008190.0000000.3022200.8330480.9857000.9780980.7797320.9923560.798343
111.00.7988351.470639-1.6359750.4537730.4256291.1048751.2823221.381664...1.1288480.9004610.0000000.9097531.1083300.9856920.9513310.8032520.8659240.780118
220.01.344385-0.8766260.9359131.9920500.8824541.786066-1.646778-0.942383...-0.678379-1.3603560.0000000.9466521.0287040.9986560.7282810.8692001.0267360.957904
331.01.1050090.3213561.5224010.882808-1.2053490.681466-1.070464-0.921871...-0.3735660.1130410.0000000.7558561.3610570.9866100.8380851.1332950.8722450.808487
440.01.595839-0.6078110.0070751.818450-0.1119060.847550-0.5664371.581239...-0.654227-1.2743453.1019610.8237610.9381910.9717580.7891760.4305530.9613570.957818
..................................................................
95951.00.7087940.8502210.6723540.948589-1.1377551.2409110.4168611.581794...1.461144-0.7588320.0000000.9716620.8563501.1340240.9499691.5948261.0486550.922793
96960.01.1350220.285319-1.1094111.088544-0.8962611.1031340.1267240.964220...-1.183070-0.9563801.5509810.8831620.9257140.9865751.0577850.5996320.8871970.970676
97971.01.1240420.3544700.0398121.1324991.6203060.9559211.3754040.415942...-0.1753541.5619160.0000000.8515531.2510611.5463950.7434750.1385500.7176250.746045
98981.00.341495-1.223359-1.3729710.9936660.6919381.0861870.318829-1.185753...1.3054060.4260110.0000001.4295100.9751000.9880901.2573371.3532081.0404130.962988
99990.01.217926-0.307828-1.6015731.532369-1.0068240.555781-0.0594390.819528...-1.4878830.8111200.0000000.6272980.8121120.9893710.7044440.5734870.7088750.764996
\n", + "

100 rows × 30 columns

\n", + "
" + ], + "text/plain": [ + " Unnamed: 0 1.000000000000000000e+00 8.692932128906250000e-01 \\\n", + "0 0 1.0 0.907542 \n", + "1 1 1.0 0.798835 \n", + "2 2 0.0 1.344385 \n", + "3 3 1.0 1.105009 \n", + "4 4 0.0 1.595839 \n", + ".. ... ... ... \n", + "95 95 1.0 0.708794 \n", + "96 96 0.0 1.135022 \n", + "97 97 1.0 1.124042 \n", + "98 98 1.0 0.341495 \n", + "99 99 0.0 1.217926 \n", + "\n", + " -6.350818276405334473e-01 2.256902605295181274e-01 \\\n", + "0 0.329147 0.359412 \n", + "1 1.470639 -1.635975 \n", + "2 -0.876626 0.935913 \n", + "3 0.321356 1.522401 \n", + "4 -0.607811 0.007075 \n", + ".. ... ... \n", + "95 0.850221 0.672354 \n", + "96 0.285319 -1.109411 \n", + "97 0.354470 0.039812 \n", + "98 -1.223359 -1.372971 \n", + "99 -0.307828 -1.601573 \n", + "\n", + " 3.274700641632080078e-01 -6.899932026863098145e-01 \\\n", + "0 1.497970 -0.313010 \n", + "1 0.453773 0.425629 \n", + "2 1.992050 0.882454 \n", + "3 0.882808 -1.205349 \n", + "4 1.818450 -0.111906 \n", + ".. ... ... \n", + "95 0.948589 -1.137755 \n", + "96 1.088544 -0.896261 \n", + "97 1.132499 1.620306 \n", + "98 0.993666 0.691938 \n", + "99 1.532369 -1.006824 \n", + "\n", + " 7.542022466659545898e-01 -2.485731393098831177e-01 \\\n", + "0 1.095531 -0.557525 \n", + "1 1.104875 1.282322 \n", + "2 1.786066 -1.646778 \n", + "3 0.681466 -1.070464 \n", + "4 0.847550 -0.566437 \n", + ".. ... ... \n", + "95 1.240911 0.416861 \n", + "96 1.103134 0.126724 \n", + "97 0.955921 1.375404 \n", + "98 1.086187 0.318829 \n", + "99 0.555781 -0.059439 \n", + "\n", + " -1.092063903808593750e+00 ... -1.045456994324922562e-02 \\\n", + "0 -1.588230 ... -1.138930 \n", + "1 1.381664 ... 1.128848 \n", + "2 -0.942383 ... -0.678379 \n", + "3 -0.921871 ... -0.373566 \n", + "4 1.581239 ... -0.654227 \n", + ".. ... ... ... \n", + "95 1.581794 ... 1.461144 \n", + "96 0.964220 ... -1.183070 \n", + "97 0.415942 ... -0.175354 \n", + "98 -1.185753 ... 1.305406 \n", + "99 0.819528 ... -1.487883 \n", + "\n", + " -4.576716944575309753e-02 3.101961374282836914e+00 \\\n", + "0 -0.000819 0.000000 \n", + "1 0.900461 0.000000 \n", + "2 -1.360356 0.000000 \n", + "3 0.113041 0.000000 \n", + "4 -1.274345 3.101961 \n", + ".. ... ... \n", + "95 -0.758832 0.000000 \n", + "96 -0.956380 1.550981 \n", + "97 1.561916 0.000000 \n", + "98 0.426011 0.000000 \n", + "99 0.811120 0.000000 \n", + "\n", + " 1.353760004043579102e+00 9.795631170272827148e-01 \\\n", + "0 0.302220 0.833048 \n", + "1 0.909753 1.108330 \n", + "2 0.946652 1.028704 \n", + "3 0.755856 1.361057 \n", + "4 0.823761 0.938191 \n", + ".. ... ... \n", + "95 0.971662 0.856350 \n", + "96 0.883162 0.925714 \n", + "97 0.851553 1.251061 \n", + "98 1.429510 0.975100 \n", + "99 0.627298 0.812112 \n", + "\n", + " 9.780761599540710449e-01 9.200048446655273438e-01 \\\n", + "0 0.985700 0.978098 \n", + "1 0.985692 0.951331 \n", + "2 0.998656 0.728281 \n", + "3 0.986610 0.838085 \n", + "4 0.971758 0.789176 \n", + ".. ... ... \n", + "95 1.134024 0.949969 \n", + "96 0.986575 1.057785 \n", + "97 1.546395 0.743475 \n", + "98 0.988090 1.257337 \n", + "99 0.989371 0.704444 \n", + "\n", + " 7.216574549674987793e-01 9.887509346008300781e-01 \\\n", + "0 0.779732 0.992356 \n", + "1 0.803252 0.865924 \n", + "2 0.869200 1.026736 \n", + "3 1.133295 0.872245 \n", + "4 0.430553 0.961357 \n", + ".. ... ... \n", + "95 1.594826 1.048655 \n", + "96 0.599632 0.887197 \n", + "97 0.138550 0.717625 \n", + "98 1.353208 1.040413 \n", + "99 0.573487 0.708875 \n", + "\n", + " 8.766783475875854492e-01 \n", + "0 0.798343 \n", + "1 0.780118 \n", + "2 0.957904 \n", + "3 0.808487 \n", + "4 0.957818 \n", + ".. ... \n", + "95 0.922793 \n", + "96 0.970676 \n", + "97 0.746045 \n", + "98 0.962988 \n", + "99 0.764996 \n", + "\n", + "[100 rows x 30 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "arc_to_parquet_run.artifact('higgs-sample').show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.py b/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.py new file mode 100644 index 00000000..d9275b7c --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/arc_to_parquet.py @@ -0,0 +1,134 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import pandas as pd +import pyarrow.parquet as pq +import pyarrow as pa +import numpy as np + + +from mlrun.execution import MLClientCtx +from mlrun.datastore import DataItem + +from typing import List +import os + + + +def _chunk_readwrite( + archive_url, + dest_path, + chunksize, + header, + encoding, + dtype, + dataset +): + """stream read and write archives + + pandas reads and parquet writes + + notes + ----- + * dest_path can be either a file.parquet, or in hte case of partitioned parquet + it will be only the destination folder of the parquet partition files + """ + pqwriter = None + header = [] + for i, df in enumerate(pd.read_csv(archive_url, chunksize=chunksize, + names=header, encoding=encoding, + dtype=dtype)): + table = pa.Table.from_pandas(df) + if i == 0: + if dataset: + header = np.copy(table.schema) + else: + pqwriter = pq.ParquetWriter(dest_path, table.schema) + if dataset: + pq.write_to_dataset(table, root_path=dest_path, partition_cols=partition_cols) + else: + pqwriter.write_table(table) + if pqwriter: + pqwriter.close() + + return header + + +def arc_to_parquet( + context: MLClientCtx, + archive_url: DataItem, + header: List[str] = [None], + chunksize: int = 0, + dtype=None, + encoding: str = "latin-1", + key: str = "data", + dataset: str = "None", + part_cols=[], + file_ext: str = "parquet", + index: bool = False, + refresh_data: bool = False, + stats: bool = False +) -> None: + """Open a file/object archive and save as a parquet file or dataset + + Notes + ----- + * this function is typically for large files, please be sure to check all settings + * partitioning requires precise specification of column types. + * the archive_url can be any file readable by pandas read_csv, which includes tar files + * if the `dataset` parameter is not empty, then a partitioned dataset will be created + instead of a single file in the folder `dataset` + * if a key exists already then it will not be re-acquired unless the `refresh_data` param + is set to `True`. This is in case the original file is corrupt, or a refresh is + required. + + :param context: the function context + :param archive_url: MLRun data input (DataItem object) + :param chunksize: (0) when > 0, row size (chunk) to retrieve + per iteration + :param dtype destination data type of specified columns + :param encoding ("latin-8") file encoding + :param key: key in artifact store (when log_data=True) + :param dataset: (None) if not None then "target_path/dataset" + is folder for partitioned files + :param part_cols: ([]) list of partitioning columns + :param file_ext: (parquet) csv/parquet file extension + :param index: (False) pandas save index option + :param refresh_data: (False) overwrite existing data at that location + :param stats: (None) calculate table stats when logging artifact + """ + base_path = context.artifact_path + os.makedirs(base_path, exist_ok=True) + + archive_url = archive_url.local() + + if dataset is not None: + dest_path = os.path.join(base_path, dataset) + exists = os.path.isdir(dest_path) + else: + dest_path = os.path.join(base_path, key + f".{file_ext}") + exists = os.path.isfile(dest_path) + + if not exists: + context.logger.info("destination file does not exist, downloading") + if chunksize > 0: + header = _chunk_readwrite(archive_url, dest_path, chunksize, + encoding, dtype, dataset) + context.log_dataset(key=key, stats=stats, format='parquet', + target_path=dest_path) + else: + df = pd.read_csv(archive_url) + context.log_dataset(key, df=df, format=file_ext, index=index) + else: + context.logger.info("destination file already exists, nothing done") \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/src/function.yaml b/functions/master/arc_to_parquet/1.5.0/src/function.yaml new file mode 100644 index 00000000..d10e841c --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/function.yaml @@ -0,0 +1,100 @@ +verbose: false +metadata: + tag: '' + name: arc-to-parquet + categories: + - utils +kind: job +spec: + command: '' + default_handler: arc_to_parquet + entry_points: + arc_to_parquet: + has_varargs: false + parameters: + - name: context + type: MLClientCtx + doc: the function context + - name: archive_url + type: DataItem + doc: MLRun data input (DataItem object) + - name: header + type: List[str] + default: + - null + - name: chunksize + type: int + doc: (0) when > 0, row size (chunk) to retrieve per iteration + default: 0 + - name: dtype + default: null + - name: encoding + type: str + default: latin-1 + - name: key + type: str + doc: key in artifact store (when log_data=True) + default: data + - name: dataset + type: str + doc: (None) if not None then "target_path/dataset" is folder for partitioned + files + default: None + - name: part_cols + doc: ([]) list of partitioning columns + default: [] + - name: file_ext + type: str + doc: (parquet) csv/parquet file extension + default: parquet + - name: index + type: bool + doc: (False) pandas save index option + default: false + - name: refresh_data + type: bool + doc: (False) overwrite existing data at that location + default: false + - name: stats + type: bool + doc: (None) calculate table stats when logging artifact + default: false + lineno: 68 + outputs: + - type: None + name: arc_to_parquet + has_kwargs: false + doc: 'Open a file/object archive and save as a parquet file or dataset + + + Notes + + ----- + + * this function is typically for large files, please be sure to check all + settings + + * partitioning requires precise specification of column types. + + * the archive_url can be any file readable by pandas read_csv, which includes + tar files + + * if the `dataset` parameter is not empty, then a partitioned dataset will + be created + + instead of a single file in the folder `dataset` + + * if a key exists already then it will not be re-acquired unless the `refresh_data` + param + + is set to `True`. This is in case the original file is corrupt, or a refresh + is + + required.' + build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ== + code_origin: '' + origin_filename: '' + description: retrieve remote archive, open and save as parquet + disable_auto_mount: false + image: mlrun/mlrun diff --git a/functions/master/arc_to_parquet/1.5.0/src/item.yaml b/functions/master/arc_to_parquet/1.5.0/src/item.yaml new file mode 100644 index 00000000..4bc2634c --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/item.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +categories: +- utils +description: retrieve remote archive, open and save as parquet +doc: '' +example: arc_to_parquet.ipynb +generationDate: 2022-08-28:17-25 +hidden: false +icon: '' +labels: + author: avi +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: arc-to-parquet +platformVersion: 3.5.4 +spec: + filename: arc_to_parquet.py + handler: arc_to_parquet + image: mlrun/mlrun + kind: job + requirements: [] +url: '' +version: 1.5.0 diff --git a/functions/master/arc_to_parquet/1.5.0/src/requirements.txt b/functions/master/arc_to_parquet/1.5.0/src/requirements.txt new file mode 100644 index 00000000..97eeefad --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/requirements.txt @@ -0,0 +1,2 @@ +pyarrow +pandas \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/src/test_arc_to_parquet.py b/functions/master/arc_to_parquet/1.5.0/src/test_arc_to_parquet.py new file mode 100644 index 00000000..f0299f57 --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/src/test_arc_to_parquet.py @@ -0,0 +1,43 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from mlrun import code_to_function, import_function + +DATA_URL = "https://s3.wasabisys.com/iguazio/data/market-palce/arc_to_parquet/higgs-sample.csv.gz" + +def test_run_arc_to_parquet(): + fn = code_to_function(name='test_arc_to_parquet', + filename="arc_to_parquet.py", + handler="arc_to_parquet", + kind="local", + ) + run = fn.run(params={"key": "higgs-sample"}, + handler="arc_to_parquet", + inputs={"archive_url": DATA_URL}, + artifact_path='artifacts', + local=False) + + assert(run.outputs['higgs-sample']) + +def test_run_local_arc_to_parquet(): + import os + os.getcwd() + fn = import_function("function.yaml") + run = fn.run(params={"key": "higgs-sample"}, + handler="arc_to_parquet", + inputs={"archive_url": DATA_URL}, + artifact_path=os.getcwd()+'/artifacts', + local=True) + + assert(run.outputs['higgs-sample']) \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/arc_to_parquet.html b/functions/master/arc_to_parquet/1.5.0/static/arc_to_parquet.html new file mode 100644 index 00000000..3598982d --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/arc_to_parquet.html @@ -0,0 +1,309 @@ + + + + + + + +arc_to_parquet.arc_to_parquet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ +
+
+
+
+
+ +
+

Source code for arc_to_parquet.arc_to_parquet

+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import pandas as pd
+import pyarrow.parquet as pq
+import pyarrow as pa
+import numpy as np
+
+
+from mlrun.execution import MLClientCtx
+from mlrun.datastore import DataItem
+
+from typing import List
+import os
+
+
+
+def _chunk_readwrite(
+        archive_url,
+        dest_path,
+        chunksize,
+        header,
+        encoding,
+        dtype,
+        dataset
+):
+    """stream read and write archives
+
+    pandas reads and parquet writes
+
+    notes
+    -----
+    * dest_path can be either a file.parquet, or in hte case of partitioned parquet
+      it will be only the destination folder of the parquet partition files
+    """
+    pqwriter = None
+    header = []
+    for i, df in enumerate(pd.read_csv(archive_url, chunksize=chunksize,
+                                       names=header, encoding=encoding,
+                                       dtype=dtype)):
+        table = pa.Table.from_pandas(df)
+        if i == 0:
+            if dataset:
+                header = np.copy(table.schema)
+            else:
+                pqwriter = pq.ParquetWriter(dest_path, table.schema)
+        if dataset:
+            pq.write_to_dataset(table, root_path=dest_path, partition_cols=partition_cols)
+        else:
+            pqwriter.write_table(table)
+    if pqwriter:
+        pqwriter.close()
+
+    return header
+
+
+
+[docs] +def arc_to_parquet( + context: MLClientCtx, + archive_url: DataItem, + header: List[str] = [None], + chunksize: int = 0, + dtype=None, + encoding: str = "latin-1", + key: str = "data", + dataset: str = "None", + part_cols=[], + file_ext: str = "parquet", + index: bool = False, + refresh_data: bool = False, + stats: bool = False +) -> None: + """Open a file/object archive and save as a parquet file or dataset + + Notes + ----- + * this function is typically for large files, please be sure to check all settings + * partitioning requires precise specification of column types. + * the archive_url can be any file readable by pandas read_csv, which includes tar files + * if the `dataset` parameter is not empty, then a partitioned dataset will be created + instead of a single file in the folder `dataset` + * if a key exists already then it will not be re-acquired unless the `refresh_data` param + is set to `True`. This is in case the original file is corrupt, or a refresh is + required. + + :param context: the function context + :param archive_url: MLRun data input (DataItem object) + :param chunksize: (0) when > 0, row size (chunk) to retrieve + per iteration + :param dtype destination data type of specified columns + :param encoding ("latin-8") file encoding + :param key: key in artifact store (when log_data=True) + :param dataset: (None) if not None then "target_path/dataset" + is folder for partitioned files + :param part_cols: ([]) list of partitioning columns + :param file_ext: (parquet) csv/parquet file extension + :param index: (False) pandas save index option + :param refresh_data: (False) overwrite existing data at that location + :param stats: (None) calculate table stats when logging artifact + """ + base_path = context.artifact_path + os.makedirs(base_path, exist_ok=True) + + archive_url = archive_url.local() + + if dataset is not None: + dest_path = os.path.join(base_path, dataset) + exists = os.path.isdir(dest_path) + else: + dest_path = os.path.join(base_path, key + f".{file_ext}") + exists = os.path.isfile(dest_path) + + if not exists: + context.logger.info("destination file does not exist, downloading") + if chunksize > 0: + header = _chunk_readwrite(archive_url, dest_path, chunksize, + encoding, dtype, dataset) + context.log_dataset(key=key, stats=stats, format='parquet', + target_path=dest_path) + else: + df = pd.read_csv(archive_url) + context.log_dataset(key, df=df, format=file_ext, index=index) + else: + context.logger.info("destination file already exists, nothing done")
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/documentation.html b/functions/master/arc_to_parquet/1.5.0/static/documentation.html new file mode 100644 index 00000000..6f217a17 --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/documentation.html @@ -0,0 +1,280 @@ + + + + + + + +arc_to_parquet package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

arc_to_parquet package

+ +
+ +
+
+ +
+
+

arc_to_parquet package#

+
+

Submodules#

+
+
+

arc_to_parquet.arc_to_parquet module#

+
+
+arc_to_parquet.arc_to_parquet.arc_to_parquet(context: MLClientCtx, archive_url: DataItem, header: List[str] = [None], chunksize: int = 0, dtype=None, encoding: str = 'latin-1', key: str = 'data', dataset: str = 'None', part_cols=[], file_ext: str = 'parquet', index: bool = False, refresh_data: bool = False, stats: bool = False) None[source]#
+

Open a file/object archive and save as a parquet file or dataset

+

Notes

+
    +
  • this function is typically for large files, please be sure to check all settings

  • +
  • partitioning requires precise specification of column types.

  • +
  • the archive_url can be any file readable by pandas read_csv, which includes tar files

  • +
  • if the dataset parameter is not empty, then a partitioned dataset will be created

  • +
+

instead of a single file in the folder dataset +* if a key exists already then it will not be re-acquired unless the refresh_data param +is set to True. This is in case the original file is corrupt, or a refresh is +required.

+
+
Parameters:
+
    +
  • context – the function context

  • +
  • archive_url – MLRun data input (DataItem object)

  • +
  • chunksize – (0) when > 0, row size (chunk) to retrieve +per iteration

  • +
+
+
+

:param dtype destination data type of specified columns +:param encoding (“latin-8”) file encoding +:param key: key in artifact store (when log_data=True) +:param dataset: (None) if not None then “target_path/dataset”

+
+

is folder for partitioned files

+
+
+
Parameters:
+
    +
  • part_cols – ([]) list of partitioning columns

  • +
  • file_ext – (parquet) csv/parquet file extension

  • +
  • index – (False) pandas save index option

  • +
  • refresh_data – (False) overwrite existing data at that location

  • +
  • stats – (None) calculate table stats when logging artifact

  • +
+
+
+
+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/example.html b/functions/master/arc_to_parquet/1.5.0/static/example.html new file mode 100644 index 00000000..3f87a94c --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/example.html @@ -0,0 +1,818 @@ + + + + + + + +Archive to parquet function Example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

Archive to parquet function Example

+ +
+
+
+

Contents

+
+ +
+
+
+ +
+
+

Archive to parquet function Example#

+
+

the arc_to_parquet function is typically for large files, the function accept an input of archive and stores the data into a file system. +in the example we will use arc_to_parquet function to unarchive the higgs-sample data-file stored on s3, +and will store it on the local file system in parquet format ,

+
+
+
+
# upload environment variables from env file if exists
+import os,mlrun
+   
+# Specify path
+path = "/tmp/examples_ci.env"
+   
+if os.path.exists(path):
+    env_dict = mlrun.set_env_from_file(path, return_dict=True)
+
+
+
+
+
+
+
# create the new project
+project_name = 'arch-to-parquet-example'
+
+# Initialize the MLRun project object
+project = mlrun.get_or_create_project(project_name, context="./", user_project=True)
+
+
+
+
+
> 2022-12-25 11:14:04,646 [info] loaded project arch-to-parquet-example from MLRun DB
+
+
+
+
+
+
+
# import packages
+import mlrun
+from mlrun import import_function
+
+
+
+
+
+
+
# declare the dataset
+DATA_URL = "https://s3.wasabisys.com/iguazio/data/market-palce/arc_to_parquet/higgs-sample.csv.gz"
+
+
+
+
+
+
+
# import the function
+arc_to_parquet_function = import_function("hub://arc_to_parquet")
+
+
+
+
+
+
+
# run the function
+arc_to_parquet_run = arc_to_parquet_function.run(params={"key": "higgs-sample"},
+           handler="arc_to_parquet",
+           inputs={"archive_url": DATA_URL}
+           )
+    
+
+
+
+
+
> 2022-12-25 11:14:05,030 [warning] it is recommended to use k8s secret (specify secret_name), specifying the aws_access_key/aws_secret_key directly is unsafe
+> 2022-12-25 11:14:05,046 [info] starting run arc-to-parquet-arc_to_parquet uid=cb1962a5333f4f9f9c16faabfd1e94c1 DB=http://mlrun-api:8080
+> 2022-12-25 11:14:05,203 [info] Job is running in the background, pod: arc-to-parquet-arc-to-parquet-8kz4b
+> 2022-12-25 11:14:44,126 [info] downloading https://s3.wasabisys.com/iguazio/data/market-palce/arc_to_parquet/higgs-sample.csv.gz to local temp file
+> 2022-12-25 11:14:44,793 [info] destination file does not exist, downloading
+> 2022-12-25 11:14:45,143 [info] To track results use the CLI: {'info_cmd': 'mlrun get run cb1962a5333f4f9f9c16faabfd1e94c1 -p arch-to-parquet-example-jovyan', 'logs_cmd': 'mlrun logs cb1962a5333f4f9f9c16faabfd1e94c1 -p arch-to-parquet-example-jovyan'}
+> 2022-12-25 11:14:45,144 [info] run executed, status=completed
+final state: completed
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
arch-to-parquet-example-jovyan0Dec 25 11:14:44completedarc-to-parquet-arc_to_parquet
kind=job
owner=jovyan
mlrun/client_version=1.2.1-rc7
host=arc-to-parquet-arc-to-parquet-8kz4b
archive_url
key=higgs-sample
higgs-sample
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2022-12-25 11:14:47,549 [info] run executed, status=completed
+
+
+
+
+
+

Show the results#

+
+
+
arc_to_parquet_run.artifact('higgs-sample').show()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unnamed: 01.000000000000000000e+008.692932128906250000e-01-6.350818276405334473e-012.256902605295181274e-013.274700641632080078e-01-6.899932026863098145e-017.542022466659545898e-01-2.485731393098831177e-01-1.092063903808593750e+00...-1.045456994324922562e-02-4.576716944575309753e-023.101961374282836914e+001.353760004043579102e+009.795631170272827148e-019.780761599540710449e-019.200048446655273438e-017.216574549674987793e-019.887509346008300781e-018.766783475875854492e-01
001.00.9075420.3291470.3594121.497970-0.3130101.095531-0.557525-1.588230...-1.138930-0.0008190.0000000.3022200.8330480.9857000.9780980.7797320.9923560.798343
111.00.7988351.470639-1.6359750.4537730.4256291.1048751.2823221.381664...1.1288480.9004610.0000000.9097531.1083300.9856920.9513310.8032520.8659240.780118
220.01.344385-0.8766260.9359131.9920500.8824541.786066-1.646778-0.942383...-0.678379-1.3603560.0000000.9466521.0287040.9986560.7282810.8692001.0267360.957904
331.01.1050090.3213561.5224010.882808-1.2053490.681466-1.070464-0.921871...-0.3735660.1130410.0000000.7558561.3610570.9866100.8380851.1332950.8722450.808487
440.01.595839-0.6078110.0070751.818450-0.1119060.847550-0.5664371.581239...-0.654227-1.2743453.1019610.8237610.9381910.9717580.7891760.4305530.9613570.957818
..................................................................
95951.00.7087940.8502210.6723540.948589-1.1377551.2409110.4168611.581794...1.461144-0.7588320.0000000.9716620.8563501.1340240.9499691.5948261.0486550.922793
96960.01.1350220.285319-1.1094111.088544-0.8962611.1031340.1267240.964220...-1.183070-0.9563801.5509810.8831620.9257140.9865751.0577850.5996320.8871970.970676
97971.01.1240420.3544700.0398121.1324991.6203060.9559211.3754040.415942...-0.1753541.5619160.0000000.8515531.2510611.5463950.7434750.1385500.7176250.746045
98981.00.341495-1.223359-1.3729710.9936660.6919381.0861870.318829-1.185753...1.3054060.4260110.0000001.4295100.9751000.9880901.2573371.3532081.0404130.962988
99990.01.217926-0.307828-1.6015731.532369-1.0068240.555781-0.0594390.819528...-1.4878830.8111200.0000000.6272980.8121120.9893710.7044440.5734870.7088750.764996
+

100 rows × 30 columns

+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/function.html b/functions/master/arc_to_parquet/1.5.0/static/function.html new file mode 100644 index 00000000..998b172d --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/function.html @@ -0,0 +1,135 @@ + + + + + + + + + + + Source + + + + +
+        
+verbose: false
+metadata:
+  tag: ''
+  name: arc-to-parquet
+  categories:
+  - utils
+kind: job
+spec:
+  command: ''
+  default_handler: arc_to_parquet
+  entry_points:
+    arc_to_parquet:
+      has_varargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: the function context
+      - name: archive_url
+        type: DataItem
+        doc: MLRun data input (DataItem object)
+      - name: header
+        type: List[str]
+        default:
+        - null
+      - name: chunksize
+        type: int
+        doc: (0) when > 0, row size (chunk) to retrieve per iteration
+        default: 0
+      - name: dtype
+        default: null
+      - name: encoding
+        type: str
+        default: latin-1
+      - name: key
+        type: str
+        doc: key in artifact store (when log_data=True)
+        default: data
+      - name: dataset
+        type: str
+        doc: (None) if not None then "target_path/dataset" is folder for partitioned
+          files
+        default: None
+      - name: part_cols
+        doc: ([]) list of partitioning columns
+        default: []
+      - name: file_ext
+        type: str
+        doc: (parquet) csv/parquet file extension
+        default: parquet
+      - name: index
+        type: bool
+        doc: (False) pandas save index option
+        default: false
+      - name: refresh_data
+        type: bool
+        doc: (False) overwrite existing data at that location
+        default: false
+      - name: stats
+        type: bool
+        doc: (None) calculate table stats when logging artifact
+        default: false
+      lineno: 68
+      outputs:
+      - type: None
+      name: arc_to_parquet
+      has_kwargs: false
+      doc: 'Open a file/object archive and save as a parquet file or dataset
+
+
+        Notes
+
+        -----
+
+        * this function is typically for large files, please be sure to check all
+        settings
+
+        * partitioning requires precise specification of column types.
+
+        * the archive_url can be any file readable by pandas read_csv, which includes
+        tar files
+
+        * if the `dataset` parameter is not empty, then a partitioned dataset will
+        be created
+
+        instead of a single file in the folder `dataset`
+
+        * if a key exists already then it will not be re-acquired unless the `refresh_data`
+        param
+
+        is set to `True`.  This is in case the original file is corrupt, or a refresh
+        is
+
+        required.'
+  build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ==
+    code_origin: ''
+    origin_filename: ''
+  description: retrieve remote archive, open and save as parquet
+  disable_auto_mount: false
+  image: mlrun/mlrun
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/item.html b/functions/master/arc_to_parquet/1.5.0/static/item.html new file mode 100644 index 00000000..f5e22a54 --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/item.html @@ -0,0 +1,59 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+- utils
+description: retrieve remote archive, open and save as parquet
+doc: ''
+example: arc_to_parquet.ipynb
+generationDate: 2022-08-28:17-25
+hidden: false
+icon: ''
+labels:
+  author: avi
+maintainers: []
+marketplaceType: ''
+mlrunVersion: 1.7.0
+name: arc-to-parquet
+platformVersion: 3.5.4
+spec:
+  filename: arc_to_parquet.py
+  handler: arc_to_parquet
+  image: mlrun/mlrun
+  kind: job
+  requirements: []
+url: ''
+version: 1.5.0
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/1.5.0/static/source.html b/functions/master/arc_to_parquet/1.5.0/static/source.html new file mode 100644 index 00000000..bc20fefd --- /dev/null +++ b/functions/master/arc_to_parquet/1.5.0/static/source.html @@ -0,0 +1,168 @@ + + + + + + + + + + + Source + + + + +
+        
+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import pandas as pd
+import pyarrow.parquet as pq
+import pyarrow as pa
+import numpy as np
+
+
+from mlrun.execution import MLClientCtx
+from mlrun.datastore import DataItem
+
+from typing import List
+import os
+
+
+
+def _chunk_readwrite(
+        archive_url,
+        dest_path,
+        chunksize,
+        header,
+        encoding,
+        dtype,
+        dataset
+):
+    """stream read and write archives
+
+    pandas reads and parquet writes
+
+    notes
+    -----
+    * dest_path can be either a file.parquet, or in hte case of partitioned parquet
+      it will be only the destination folder of the parquet partition files
+    """
+    pqwriter = None
+    header = []
+    for i, df in enumerate(pd.read_csv(archive_url, chunksize=chunksize,
+                                       names=header, encoding=encoding,
+                                       dtype=dtype)):
+        table = pa.Table.from_pandas(df)
+        if i == 0:
+            if dataset:
+                header = np.copy(table.schema)
+            else:
+                pqwriter = pq.ParquetWriter(dest_path, table.schema)
+        if dataset:
+            pq.write_to_dataset(table, root_path=dest_path, partition_cols=partition_cols)
+        else:
+            pqwriter.write_table(table)
+    if pqwriter:
+        pqwriter.close()
+
+    return header
+
+
+def arc_to_parquet(
+        context: MLClientCtx,
+        archive_url: DataItem,
+        header: List[str] = [None],
+        chunksize: int = 0,
+        dtype=None,
+        encoding: str = "latin-1",
+        key: str = "data",
+        dataset: str = "None",
+        part_cols=[],
+        file_ext: str = "parquet",
+        index: bool = False,
+        refresh_data: bool = False,
+        stats: bool = False
+) -> None:
+    """Open a file/object archive and save as a parquet file or dataset
+
+    Notes
+    -----
+    * this function is typically for large files, please be sure to check all settings
+    * partitioning requires precise specification of column types.
+    * the archive_url can be any file readable by pandas read_csv, which includes tar files
+    * if the `dataset` parameter is not empty, then a partitioned dataset will be created
+    instead of a single file in the folder `dataset`
+    * if a key exists already then it will not be re-acquired unless the `refresh_data` param
+    is set to `True`.  This is in case the original file is corrupt, or a refresh is
+    required.
+
+    :param context:        the function context
+    :param archive_url:    MLRun data input (DataItem object)
+    :param chunksize:      (0) when > 0, row size (chunk) to retrieve
+                           per iteration
+    :param dtype           destination data type of specified columns
+    :param encoding        ("latin-8") file encoding
+    :param key:            key in artifact store (when log_data=True)
+    :param dataset:        (None) if not None then "target_path/dataset"
+                           is folder for partitioned files
+    :param part_cols:      ([]) list of partitioning columns
+    :param file_ext:       (parquet) csv/parquet file extension
+    :param index:          (False) pandas save index option
+    :param refresh_data:   (False) overwrite existing data at that location
+    :param stats:          (None) calculate table stats when logging artifact
+    """
+    base_path = context.artifact_path
+    os.makedirs(base_path, exist_ok=True)
+
+    archive_url = archive_url.local()
+
+    if dataset is not None:
+        dest_path = os.path.join(base_path, dataset)
+        exists = os.path.isdir(dest_path)
+    else:
+        dest_path = os.path.join(base_path, key + f".{file_ext}")
+        exists = os.path.isfile(dest_path)
+
+    if not exists:
+        context.logger.info("destination file does not exist, downloading")
+        if chunksize > 0:
+            header = _chunk_readwrite(archive_url, dest_path, chunksize,
+                                      encoding, dtype, dataset)
+            context.log_dataset(key=key, stats=stats, format='parquet',
+                                target_path=dest_path)
+        else:
+            df = pd.read_csv(archive_url)
+            context.log_dataset(key, df=df, format=file_ext, index=index)
+    else:
+        context.logger.info("destination file already exists, nothing done")
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/arc_to_parquet/latest/src/function.yaml b/functions/master/arc_to_parquet/latest/src/function.yaml index f76d0494..d10e841c 100644 --- a/functions/master/arc_to_parquet/latest/src/function.yaml +++ b/functions/master/arc_to_parquet/latest/src/function.yaml @@ -1,62 +1,23 @@ -kind: job +verbose: false metadata: - name: arc-to-parquet tag: '' - hash: 959e5c3513bb7568402b6ce4023f4615e224b566 - project: '' - labels: - author: avi + name: arc-to-parquet categories: - - etl + - utils +kind: job spec: command: '' - args: [] - image: mlrun/mlrun - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ== - commands: [] - code_origin: http://github.com/aviaIguazio/functions.git#b32ae36ee9e5fb7a3b0affa8c15046aae9df7d24:/Users/Avi_Asulin/PycharmProjects/functions/arc_to_parquet/arc_to_parquet.py - origin_filename: /Users/Avi_Asulin/PycharmProjects/functions/arc_to_parquet/arc_to_parquet.py - requirements: [] + default_handler: arc_to_parquet entry_points: arc_to_parquet: - name: arc_to_parquet - doc: 'Open a file/object archive and save as a parquet file or dataset - - - Notes - - ----- - - * this function is typically for large files, please be sure to check all - settings - - * partitioning requires precise specification of column types. - - * the archive_url can be any file readable by pandas read_csv, which includes - tar files - - * if the `dataset` parameter is not empty, then a partitioned dataset will - be created - - instead of a single file in the folder `dataset` - - * if a key exists already then it will not be re-acquired unless the `refresh_data` - param - - is set to `True`. This is in case the original file is corrupt, or a refresh - is - - required.' + has_varargs: false parameters: - name: context type: MLClientCtx doc: the function context - default: '' - name: archive_url type: DataItem doc: MLRun data input (DataItem object) - default: '' - name: header type: List[str] default: @@ -98,17 +59,42 @@ spec: type: bool doc: (None) calculate table stats when logging artifact default: false - outputs: - - default: '' lineno: 68 + outputs: + - type: None + name: arc_to_parquet + has_kwargs: false + doc: 'Open a file/object archive and save as a parquet file or dataset + + + Notes + + ----- + + * this function is typically for large files, please be sure to check all + settings + + * partitioning requires precise specification of column types. + + * the archive_url can be any file readable by pandas read_csv, which includes + tar files + + * if the `dataset` parameter is not empty, then a partitioned dataset will + be created + + instead of a single file in the folder `dataset` + + * if a key exists already then it will not be re-acquired unless the `refresh_data` + param + + is set to `True`. This is in case the original file is corrupt, or a refresh + is + + required.' + build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ== + code_origin: '' + origin_filename: '' description: retrieve remote archive, open and save as parquet - default_handler: arc_to_parquet disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false + image: mlrun/mlrun diff --git a/functions/master/arc_to_parquet/latest/src/item.yaml b/functions/master/arc_to_parquet/latest/src/item.yaml index e08535f9..4bc2634c 100644 --- a/functions/master/arc_to_parquet/latest/src/item.yaml +++ b/functions/master/arc_to_parquet/latest/src/item.yaml @@ -1,6 +1,6 @@ apiVersion: v1 categories: -- etl +- utils description: retrieve remote archive, open and save as parquet doc: '' example: arc_to_parquet.ipynb @@ -11,7 +11,7 @@ labels: author: avi maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.1 +mlrunVersion: 1.7.0 name: arc-to-parquet platformVersion: 3.5.4 spec: @@ -21,4 +21,4 @@ spec: kind: job requirements: [] url: '' -version: 1.4.1 +version: 1.5.0 diff --git a/functions/master/arc_to_parquet/latest/static/arc_to_parquet.html b/functions/master/arc_to_parquet/latest/static/arc_to_parquet.html index 40fcaf4b..3598982d 100644 --- a/functions/master/arc_to_parquet/latest/static/arc_to_parquet.html +++ b/functions/master/arc_to_parquet/latest/static/arc_to_parquet.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/arc_to_parquet/latest/static/documentation.html b/functions/master/arc_to_parquet/latest/static/documentation.html index 31c44a7a..6f217a17 100644 --- a/functions/master/arc_to_parquet/latest/static/documentation.html +++ b/functions/master/arc_to_parquet/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/arc_to_parquet/latest/static/example.html b/functions/master/arc_to_parquet/latest/static/example.html index d0a60d36..3f87a94c 100644 --- a/functions/master/arc_to_parquet/latest/static/example.html +++ b/functions/master/arc_to_parquet/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/arc_to_parquet/latest/static/function.html b/functions/master/arc_to_parquet/latest/static/function.html index 690f9b29..998b172d 100644 --- a/functions/master/arc_to_parquet/latest/static/function.html +++ b/functions/master/arc_to_parquet/latest/static/function.html @@ -28,65 +28,26 @@
         
-kind: job
+verbose: false
 metadata:
-  name: arc-to-parquet
   tag: ''
-  hash: 959e5c3513bb7568402b6ce4023f4615e224b566
-  project: ''
-  labels:
-    author: avi
+  name: arc-to-parquet
   categories:
-  - etl
+  - utils
+kind: job
 spec:
   command: ''
-  args: []
-  image: mlrun/mlrun
-  build:
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ==
-    commands: []
-    code_origin: http://github.com/aviaIguazio/functions.git#b32ae36ee9e5fb7a3b0affa8c15046aae9df7d24:/Users/Avi_Asulin/PycharmProjects/functions/arc_to_parquet/arc_to_parquet.py
-    origin_filename: /Users/Avi_Asulin/PycharmProjects/functions/arc_to_parquet/arc_to_parquet.py
-    requirements: []
+  default_handler: arc_to_parquet
   entry_points:
     arc_to_parquet:
-      name: arc_to_parquet
-      doc: 'Open a file/object archive and save as a parquet file or dataset
-
-
-        Notes
-
-        -----
-
-        * this function is typically for large files, please be sure to check all
-        settings
-
-        * partitioning requires precise specification of column types.
-
-        * the archive_url can be any file readable by pandas read_csv, which includes
-        tar files
-
-        * if the `dataset` parameter is not empty, then a partitioned dataset will
-        be created
-
-        instead of a single file in the folder `dataset`
-
-        * if a key exists already then it will not be re-acquired unless the `refresh_data`
-        param
-
-        is set to `True`.  This is in case the original file is corrupt, or a refresh
-        is
-
-        required.'
+      has_varargs: false
       parameters:
       - name: context
         type: MLClientCtx
         doc: the function context
-        default: ''
       - name: archive_url
         type: DataItem
         doc: MLRun data input (DataItem object)
-        default: ''
       - name: header
         type: List[str]
         default:
@@ -128,20 +89,45 @@
         type: bool
         doc: (None) calculate table stats when logging artifact
         default: false
-      outputs:
-      - default: ''
       lineno: 68
+      outputs:
+      - type: None
+      name: arc_to_parquet
+      has_kwargs: false
+      doc: 'Open a file/object archive and save as a parquet file or dataset
+
+
+        Notes
+
+        -----
+
+        * this function is typically for large files, please be sure to check all
+        settings
+
+        * partitioning requires precise specification of column types.
+
+        * the archive_url can be any file readable by pandas read_csv, which includes
+        tar files
+
+        * if the `dataset` parameter is not empty, then a partitioned dataset will
+        be created
+
+        instead of a single file in the folder `dataset`
+
+        * if a key exists already then it will not be re-acquired unless the `refresh_data`
+        param
+
+        is set to `True`.  This is in case the original file is corrupt, or a refresh
+        is
+
+        required.'
+  build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgcHlhcnJvdy5wYXJxdWV0IGFzIHBxCmltcG9ydCBweWFycm93IGFzIHBhCmltcG9ydCBudW1weSBhcyBucAoKCmZyb20gbWxydW4uZXhlY3V0aW9uIGltcG9ydCBNTENsaWVudEN0eApmcm9tIG1scnVuLmRhdGFzdG9yZSBpbXBvcnQgRGF0YUl0ZW0KCmZyb20gdHlwaW5nIGltcG9ydCBMaXN0CmltcG9ydCBvcwoKCgpkZWYgX2NodW5rX3JlYWR3cml0ZSgKICAgICAgICBhcmNoaXZlX3VybCwKICAgICAgICBkZXN0X3BhdGgsCiAgICAgICAgY2h1bmtzaXplLAogICAgICAgIGhlYWRlciwKICAgICAgICBlbmNvZGluZywKICAgICAgICBkdHlwZSwKICAgICAgICBkYXRhc2V0Cik6CiAgICAiIiJzdHJlYW0gcmVhZCBhbmQgd3JpdGUgYXJjaGl2ZXMKCiAgICBwYW5kYXMgcmVhZHMgYW5kIHBhcnF1ZXQgd3JpdGVzCgogICAgbm90ZXMKICAgIC0tLS0tCiAgICAqIGRlc3RfcGF0aCBjYW4gYmUgZWl0aGVyIGEgZmlsZS5wYXJxdWV0LCBvciBpbiBodGUgY2FzZSBvZiBwYXJ0aXRpb25lZCBwYXJxdWV0CiAgICAgIGl0IHdpbGwgYmUgb25seSB0aGUgZGVzdGluYXRpb24gZm9sZGVyIG9mIHRoZSBwYXJxdWV0IHBhcnRpdGlvbiBmaWxlcwogICAgIiIiCiAgICBwcXdyaXRlciA9IE5vbmUKICAgIGhlYWRlciA9IFtdCiAgICBmb3IgaSwgZGYgaW4gZW51bWVyYXRlKHBkLnJlYWRfY3N2KGFyY2hpdmVfdXJsLCBjaHVua3NpemU9Y2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1oZWFkZXIsIGVuY29kaW5nPWVuY29kaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1kdHlwZSkpOgogICAgICAgIHRhYmxlID0gcGEuVGFibGUuZnJvbV9wYW5kYXMoZGYpCiAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICBpZiBkYXRhc2V0OgogICAgICAgICAgICAgICAgaGVhZGVyID0gbnAuY29weSh0YWJsZS5zY2hlbWEpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcXdyaXRlciA9IHBxLlBhcnF1ZXRXcml0ZXIoZGVzdF9wYXRoLCB0YWJsZS5zY2hlbWEpCiAgICAgICAgaWYgZGF0YXNldDoKICAgICAgICAgICAgcHEud3JpdGVfdG9fZGF0YXNldCh0YWJsZSwgcm9vdF9wYXRoPWRlc3RfcGF0aCwgcGFydGl0aW9uX2NvbHM9cGFydGl0aW9uX2NvbHMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHF3cml0ZXIud3JpdGVfdGFibGUodGFibGUpCiAgICBpZiBwcXdyaXRlcjoKICAgICAgICBwcXdyaXRlci5jbG9zZSgpCgogICAgcmV0dXJuIGhlYWRlcgoKCmRlZiBhcmNfdG9fcGFycXVldCgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgaGVhZGVyOiBMaXN0W3N0cl0gPSBbTm9uZV0sCiAgICAgICAgY2h1bmtzaXplOiBpbnQgPSAwLAogICAgICAgIGR0eXBlPU5vbmUsCiAgICAgICAgZW5jb2Rpbmc6IHN0ciA9ICJsYXRpbi0xIiwKICAgICAgICBrZXk6IHN0ciA9ICJkYXRhIiwKICAgICAgICBkYXRhc2V0OiBzdHIgPSAiTm9uZSIsCiAgICAgICAgcGFydF9jb2xzPVtdLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgaW5kZXg6IGJvb2wgPSBGYWxzZSwKICAgICAgICByZWZyZXNoX2RhdGE6IGJvb2wgPSBGYWxzZSwKICAgICAgICBzdGF0czogYm9vbCA9IEZhbHNlCikgLT4gTm9uZToKICAgICIiIk9wZW4gYSBmaWxlL29iamVjdCBhcmNoaXZlIGFuZCBzYXZlIGFzIGEgcGFycXVldCBmaWxlIG9yIGRhdGFzZXQKCiAgICBOb3RlcwogICAgLS0tLS0KICAgICogdGhpcyBmdW5jdGlvbiBpcyB0eXBpY2FsbHkgZm9yIGxhcmdlIGZpbGVzLCBwbGVhc2UgYmUgc3VyZSB0byBjaGVjayBhbGwgc2V0dGluZ3MKICAgICogcGFydGl0aW9uaW5nIHJlcXVpcmVzIHByZWNpc2Ugc3BlY2lmaWNhdGlvbiBvZiBjb2x1bW4gdHlwZXMuCiAgICAqIHRoZSBhcmNoaXZlX3VybCBjYW4gYmUgYW55IGZpbGUgcmVhZGFibGUgYnkgcGFuZGFzIHJlYWRfY3N2LCB3aGljaCBpbmNsdWRlcyB0YXIgZmlsZXMKICAgICogaWYgdGhlIGBkYXRhc2V0YCBwYXJhbWV0ZXIgaXMgbm90IGVtcHR5LCB0aGVuIGEgcGFydGl0aW9uZWQgZGF0YXNldCB3aWxsIGJlIGNyZWF0ZWQKICAgIGluc3RlYWQgb2YgYSBzaW5nbGUgZmlsZSBpbiB0aGUgZm9sZGVyIGBkYXRhc2V0YAogICAgKiBpZiBhIGtleSBleGlzdHMgYWxyZWFkeSB0aGVuIGl0IHdpbGwgbm90IGJlIHJlLWFjcXVpcmVkIHVubGVzcyB0aGUgYHJlZnJlc2hfZGF0YWAgcGFyYW0KICAgIGlzIHNldCB0byBgVHJ1ZWAuICBUaGlzIGlzIGluIGNhc2UgdGhlIG9yaWdpbmFsIGZpbGUgaXMgY29ycnVwdCwgb3IgYSByZWZyZXNoIGlzCiAgICByZXF1aXJlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgIHRoZSBmdW5jdGlvbiBjb250ZXh0CiAgICA6cGFyYW0gYXJjaGl2ZV91cmw6ICAgIE1MUnVuIGRhdGEgaW5wdXQgKERhdGFJdGVtIG9iamVjdCkKICAgIDpwYXJhbSBjaHVua3NpemU6ICAgICAgKDApIHdoZW4gPiAwLCByb3cgc2l6ZSAoY2h1bmspIHRvIHJldHJpZXZlCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlciBpdGVyYXRpb24KICAgIDpwYXJhbSBkdHlwZSAgICAgICAgICAgZGVzdGluYXRpb24gZGF0YSB0eXBlIG9mIHNwZWNpZmllZCBjb2x1bW5zCiAgICA6cGFyYW0gZW5jb2RpbmcgICAgICAgICgibGF0aW4tOCIpIGZpbGUgZW5jb2RpbmcKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgICAga2V5IGluIGFydGlmYWN0IHN0b3JlICh3aGVuIGxvZ19kYXRhPVRydWUpCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgIChOb25lKSBpZiBub3QgTm9uZSB0aGVuICJ0YXJnZXRfcGF0aC9kYXRhc2V0IgogICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBmb2xkZXIgZm9yIHBhcnRpdGlvbmVkIGZpbGVzCiAgICA6cGFyYW0gcGFydF9jb2xzOiAgICAgIChbXSkgbGlzdCBvZiBwYXJ0aXRpb25pbmcgY29sdW1ucwogICAgOnBhcmFtIGZpbGVfZXh0OiAgICAgICAocGFycXVldCkgY3N2L3BhcnF1ZXQgZmlsZSBleHRlbnNpb24KICAgIDpwYXJhbSBpbmRleDogICAgICAgICAgKEZhbHNlKSBwYW5kYXMgc2F2ZSBpbmRleCBvcHRpb24KICAgIDpwYXJhbSByZWZyZXNoX2RhdGE6ICAgKEZhbHNlKSBvdmVyd3JpdGUgZXhpc3RpbmcgZGF0YSBhdCB0aGF0IGxvY2F0aW9uCiAgICA6cGFyYW0gc3RhdHM6ICAgICAgICAgIChOb25lKSBjYWxjdWxhdGUgdGFibGUgc3RhdHMgd2hlbiBsb2dnaW5nIGFydGlmYWN0CiAgICAiIiIKICAgIGJhc2VfcGF0aCA9IGNvbnRleHQuYXJ0aWZhY3RfcGF0aAogICAgb3MubWFrZWRpcnMoYmFzZV9wYXRoLCBleGlzdF9vaz1UcnVlKQoKICAgIGFyY2hpdmVfdXJsID0gYXJjaGl2ZV91cmwubG9jYWwoKQoKICAgIGlmIGRhdGFzZXQgaXMgbm90IE5vbmU6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwgZGF0YXNldCkKICAgICAgICBleGlzdHMgPSBvcy5wYXRoLmlzZGlyKGRlc3RfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgZGVzdF9wYXRoID0gb3MucGF0aC5qb2luKGJhc2VfcGF0aCwga2V5ICsgZiIue2ZpbGVfZXh0fSIpCiAgICAgICAgZXhpc3RzID0gb3MucGF0aC5pc2ZpbGUoZGVzdF9wYXRoKQoKICAgIGlmIG5vdCBleGlzdHM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiZGVzdGluYXRpb24gZmlsZSBkb2VzIG5vdCBleGlzdCwgZG93bmxvYWRpbmciKQogICAgICAgIGlmIGNodW5rc2l6ZSA+IDA6CiAgICAgICAgICAgIGhlYWRlciA9IF9jaHVua19yZWFkd3JpdGUoYXJjaGl2ZV91cmwsIGRlc3RfcGF0aCwgY2h1bmtzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuY29kaW5nLCBkdHlwZSwgZGF0YXNldCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXk9a2V5LCBzdGF0cz1zdGF0cywgZm9ybWF0PSdwYXJxdWV0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRfcGF0aD1kZXN0X3BhdGgpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgZGYgPSBwZC5yZWFkX2NzdihhcmNoaXZlX3VybCkKICAgICAgICAgICAgY29udGV4dC5sb2dfZGF0YXNldChrZXksIGRmPWRmLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PWluZGV4KQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJkZXN0aW5hdGlvbiBmaWxlIGFscmVhZHkgZXhpc3RzLCBub3RoaW5nIGRvbmUiKQ==
+    code_origin: ''
+    origin_filename: ''
   description: retrieve remote archive, open and save as parquet
-  default_handler: arc_to_parquet
   disable_auto_mount: false
-  clone_target_dir: ''
-  env: []
-  priority_class_name: ''
-  preemption_mode: prevent
-  affinity: null
-  tolerations: null
-  security_context: {}
-verbose: false
+  image: mlrun/mlrun
 
         
     
diff --git a/functions/master/arc_to_parquet/latest/static/item.html b/functions/master/arc_to_parquet/latest/static/item.html index 3d2efcf4..f5e22a54 100644 --- a/functions/master/arc_to_parquet/latest/static/item.html +++ b/functions/master/arc_to_parquet/latest/static/item.html @@ -30,7 +30,7 @@ apiVersion: v1 categories: -- etl +- utils description: retrieve remote archive, open and save as parquet doc: '' example: arc_to_parquet.ipynb @@ -41,7 +41,7 @@ author: avi maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.1 +mlrunVersion: 1.7.0 name: arc-to-parquet platformVersion: 3.5.4 spec: @@ -51,7 +51,7 @@ kind: job requirements: [] url: '' -version: 1.4.1 +version: 1.5.0 diff --git a/functions/master/auto_trainer/1.7.0/static/auto_trainer.html b/functions/master/auto_trainer/1.7.0/static/auto_trainer.html index cb1160d7..795a4a23 100644 --- a/functions/master/auto_trainer/1.7.0/static/auto_trainer.html +++ b/functions/master/auto_trainer/1.7.0/static/auto_trainer.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/auto_trainer/1.7.0/static/documentation.html b/functions/master/auto_trainer/1.7.0/static/documentation.html index 2e4a160d..b0bdf6b4 100644 --- a/functions/master/auto_trainer/1.7.0/static/documentation.html +++ b/functions/master/auto_trainer/1.7.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/auto_trainer/1.7.0/static/example.html b/functions/master/auto_trainer/1.7.0/static/example.html index 46a819e9..2c4fdabf 100644 --- a/functions/master/auto_trainer/1.7.0/static/example.html +++ b/functions/master/auto_trainer/1.7.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/auto_trainer/latest/static/auto_trainer.html b/functions/master/auto_trainer/latest/static/auto_trainer.html index cb1160d7..795a4a23 100644 --- a/functions/master/auto_trainer/latest/static/auto_trainer.html +++ b/functions/master/auto_trainer/latest/static/auto_trainer.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/auto_trainer/latest/static/documentation.html b/functions/master/auto_trainer/latest/static/documentation.html index 2e4a160d..b0bdf6b4 100644 --- a/functions/master/auto_trainer/latest/static/documentation.html +++ b/functions/master/auto_trainer/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/auto_trainer/latest/static/example.html b/functions/master/auto_trainer/latest/static/example.html index 46a819e9..2c4fdabf 100644 --- a/functions/master/auto_trainer/latest/static/example.html +++ b/functions/master/auto_trainer/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_serving/1.1.0/src/function.yaml b/functions/master/azureml_serving/1.1.0/src/function.yaml index c558e625..26229e70 100644 --- a/functions/master/azureml_serving/1.1.0/src/function.yaml +++ b/functions/master/azureml_serving/1.1.0/src/function.yaml @@ -48,4 +48,4 @@ spec: secret_sources: [] affinity: null tolerations: null -verbose: false +verbose: false \ No newline at end of file diff --git a/functions/master/azureml_serving/1.1.0/src/item.yaml b/functions/master/azureml_serving/1.1.0/src/item.yaml index 84fadd55..d20e636b 100644 --- a/functions/master/azureml_serving/1.1.0/src/item.yaml +++ b/functions/master/azureml_serving/1.1.0/src/item.yaml @@ -24,4 +24,4 @@ spec: requirements: - azureml-automl-runtime~=1.38.1 url: '' -version: 1.1.0 +version: 1.1.0 \ No newline at end of file diff --git a/functions/master/azureml_serving/1.1.0/static/documentation.html b/functions/master/azureml_serving/1.1.0/static/documentation.html index c5194e21..92c7c89d 100644 --- a/functions/master/azureml_serving/1.1.0/static/documentation.html +++ b/functions/master/azureml_serving/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_serving/1.1.0/static/example.html b/functions/master/azureml_serving/1.1.0/static/example.html index ebc576a1..52c450d9 100644 --- a/functions/master/azureml_serving/1.1.0/static/example.html +++ b/functions/master/azureml_serving/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_serving/1.1.0/static/function.html b/functions/master/azureml_serving/1.1.0/static/function.html index a792e369..a2b3e82d 100644 --- a/functions/master/azureml_serving/1.1.0/static/function.html +++ b/functions/master/azureml_serving/1.1.0/static/function.html @@ -79,7 +79,6 @@ affinity: null tolerations: null verbose: false - diff --git a/functions/master/azureml_serving/1.1.0/static/item.html b/functions/master/azureml_serving/1.1.0/static/item.html index 062811e0..6e8b05b3 100644 --- a/functions/master/azureml_serving/1.1.0/static/item.html +++ b/functions/master/azureml_serving/1.1.0/static/item.html @@ -55,7 +55,6 @@ - azureml-automl-runtime~=1.38.1 url: '' version: 1.1.0 - diff --git a/functions/master/azureml_serving/latest/src/function.yaml b/functions/master/azureml_serving/latest/src/function.yaml index c558e625..26229e70 100644 --- a/functions/master/azureml_serving/latest/src/function.yaml +++ b/functions/master/azureml_serving/latest/src/function.yaml @@ -48,4 +48,4 @@ spec: secret_sources: [] affinity: null tolerations: null -verbose: false +verbose: false \ No newline at end of file diff --git a/functions/master/azureml_serving/latest/src/item.yaml b/functions/master/azureml_serving/latest/src/item.yaml index 84fadd55..d20e636b 100644 --- a/functions/master/azureml_serving/latest/src/item.yaml +++ b/functions/master/azureml_serving/latest/src/item.yaml @@ -24,4 +24,4 @@ spec: requirements: - azureml-automl-runtime~=1.38.1 url: '' -version: 1.1.0 +version: 1.1.0 \ No newline at end of file diff --git a/functions/master/azureml_serving/latest/static/documentation.html b/functions/master/azureml_serving/latest/static/documentation.html index c5194e21..92c7c89d 100644 --- a/functions/master/azureml_serving/latest/static/documentation.html +++ b/functions/master/azureml_serving/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_serving/latest/static/example.html b/functions/master/azureml_serving/latest/static/example.html index ebc576a1..52c450d9 100644 --- a/functions/master/azureml_serving/latest/static/example.html +++ b/functions/master/azureml_serving/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_serving/latest/static/function.html b/functions/master/azureml_serving/latest/static/function.html index a792e369..a2b3e82d 100644 --- a/functions/master/azureml_serving/latest/static/function.html +++ b/functions/master/azureml_serving/latest/static/function.html @@ -79,7 +79,6 @@ affinity: null tolerations: null verbose: false - diff --git a/functions/master/azureml_serving/latest/static/item.html b/functions/master/azureml_serving/latest/static/item.html index 062811e0..6e8b05b3 100644 --- a/functions/master/azureml_serving/latest/static/item.html +++ b/functions/master/azureml_serving/latest/static/item.html @@ -55,7 +55,6 @@ - azureml-automl-runtime~=1.38.1 url: '' version: 1.1.0 - diff --git a/functions/master/azureml_utils/1.4.0/src/azureml_utils.ipynb b/functions/master/azureml_utils/1.4.0/src/azureml_utils.ipynb new file mode 100644 index 00000000..3ab6f766 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/azureml_utils.ipynb @@ -0,0 +1,1899 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# AzureML AutoML Demo\n", + "MLRun function for using Azure AutoML, Including the following handlers:\n", + "1. `init_experiment` - Initialize workspace and experiment in Azure ML.\n", + "2. `init_compute` - Initialize Azure ML compute target to run experiment.\n", + "3. `register_dataset` - Register dataset object (can be also an Iguazio FeatureVector) in Azure ML.\n", + "4. `download_model` - Download trained model from Azure ML to local filesystem.\n", + "5. `upload_model` - Upload pre-trained model from local filesystem to Azure ML.\n", + "6. `submit_training_job` - Submit training job to Azure AutoML and download trained model when completed.\n", + "7. `automl_train` - Whole training flow for Azure AutoML:\n", + " - Initializing workspace and experiment in Azure ML\n", + " - Registers dataset/feature vector,\n", + " - submits training job\n", + " - downloads trained model" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 1. Setup MLRun Project\n", + "\n", + "Creating MLRun project" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 1, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-02-02 18:28:06,840 [warning] Failed resolving version info. Ignoring and using defaults\n", + "> 2022-02-02 18:28:11,379 [warning] Server or client version is unstable. Assuming compatible: {'server_version': '0.0.0+unstable', 'client_version': '0.0.0+unstable'}\n" + ] + } + ], + "source": [ + "import mlrun" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-02-02 18:28:11,423 [info] loaded project azureml from MLRun DB\n" + ] + } + ], + "source": [ + "# Initialize the MLRun project object\n", + "project = mlrun.get_or_create_project('azureml', context=\"./\", user_project=True)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 2. Preparing Dataset (Iris)\n", + "\n", + "- Preparing training URI for the MLRun function" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)label
05.13.51.40.20
14.93.01.40.20
24.73.21.30.20
34.63.11.50.20
45.03.61.40.20
\n", + "
" + ], + "text/plain": [ + " sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) \\\n", + "0 5.1 3.5 1.4 0.2 \n", + "1 4.9 3.0 1.4 0.2 \n", + "2 4.7 3.2 1.3 0.2 \n", + "3 4.6 3.1 1.5 0.2 \n", + "4 5.0 3.6 1.4 0.2 \n", + "\n", + " label \n", + "0 0 \n", + "1 0 \n", + "2 0 \n", + "3 0 \n", + "4 0 " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DATA_URL = \"https://s3.wasabisys.com/iguazio/data/iris/iris_dataset.csv\"\n", + "\n", + "mlrun.get_dataitem(DATA_URL).as_df().head()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 3. Submit Azure AutoML Training Job" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Submit Azure Secrets\n", + "For more information about working with secrets see: [MLRun docs: Working with secrets](https://docs.mlrun.org/en/latest/secrets.html)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [], + "source": [ + "project.set_secrets(file_path=\"env\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Import `azureml_utils` from marketplace" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [], + "source": [ + "azureml_fn = mlrun.import_function('hub://azureml_utils')\n", + "azureml_fn.deploy()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Automl configuration & run parameters\n", + "\n", + "- The `automl_settings` object is the setup for Azure AutoML. It holds the `task` type, number of models to train - `iterations`, the desired metric - `primary metric`, the allowed types of models `allowed_models` and more.\n", + "\n", + "- The `params` are the parameters for the MLRun function, such as experiment (`experiment_name`) and cpu cluster (`cpu_cluster_name`) names in AzureML, dataset properties for registration, target label for training - `label_column_name`, number of models to download `save_n_models` and more." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [], + "source": [ + "label_column_name = 'label' # target label\n", + "\n", + "# Configure automl settings:\n", + "automl_settings = {\n", + " \"task\": 'classification',\n", + " \"debug_log\": 'automl_errors.log',\n", + "# \"experiment_exit_score\" : 0.9,\n", + " \"enable_early_stopping\": False,\n", + " \"allowed_models\": ['LogisticRegression', 'SGD', 'SVM'],\n", + " \"iterations\": 5,\n", + " \"iteration_timeout_minutes\": 2,\n", + " \"max_concurrent_iterations\": 2,\n", + " \"max_cores_per_iteration\": -1,\n", + " \"n_cross_validations\": 5,\n", + " \"primary_metric\": 'accuracy',\n", + " \"featurization\": 'off',\n", + " \"model_explainability\": False,\n", + " \"enable_voting_ensemble\": False,\n", + " \"enable_stack_ensemble\": False\n", + " }\n", + "\n", + "# Setting params to azure_run function:\n", + "params = {\n", + " \"experiment_name\": 'azure-automl-test',\n", + " \"cpu_cluster_name\": 'azureml-cpu',\n", + " \"dataset_name\": 'iris',\n", + " \"dataset_description\": 'iris training data',\n", + " \"label_column_name\": label_column_name,\n", + " \"create_new_version\": True,\n", + " \"register_model_name\": \"iris-model\",\n", + " \"save_n_models\": 3,\n", + " \"automl_settings\": automl_settings\n", + "}" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Run Azure AutoML train:\n", + "\n", + "This MLRun function will perform the following:\n", + "- Initialize workspace and experiment in your AzureML\n", + "- Register the dataset/feature vector to Iguazio and to AzureML.\n", + "- Submit the training job to AzureML and print the live training results fro each model\n", + "- Generate the top trained models." + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-02-02 18:28:11,740 [info] Function is not deployed and auto_build flag is set, starting deploy...\n", + "> 2022-02-02 18:28:11,932 [info] Started building image: .mlrun/func-azureml-yonatan-azureml-utils:latest\n", + "\u001B[36mINFO\u001B[0m[0000] Retrieving image manifest python:3.7.9-slim \n", + "\u001B[36mINFO\u001B[0m[0000] Retrieving image python:3.7.9-slim from registry index.docker.io \n", + "\u001B[36mINFO\u001B[0m[0000] Built cross stage deps: map[] \n", + "\u001B[36mINFO\u001B[0m[0000] Retrieving image manifest python:3.7.9-slim \n", + "\u001B[36mINFO\u001B[0m[0000] Returning cached image manifest \n", + "\u001B[36mINFO\u001B[0m[0000] Executing 0 build triggers \n", + "\u001B[36mINFO\u001B[0m[0000] Unpacking rootfs as cmd RUN python -m pip install pip==21.2.4 requires it. \n", + "\u001B[36mINFO\u001B[0m[0002] RUN python -m pip install pip==21.2.4 \n", + "\u001B[36mINFO\u001B[0m[0002] Taking snapshot of full filesystem... \n", + "\u001B[36mINFO\u001B[0m[0003] cmd: /bin/sh \n", + "\u001B[36mINFO\u001B[0m[0003] args: [-c python -m pip install pip==21.2.4] \n", + "\u001B[36mINFO\u001B[0m[0003] Running: [/bin/sh -c python -m pip install pip==21.2.4] \n", + "Collecting pip==21.2.4\n", + " Downloading pip-21.2.4-py3-none-any.whl (1.6 MB)\n", + "Installing collected packages: pip\n", + " Attempting uninstall: pip\n", + " Found existing installation: pip 21.0.1\n", + " Uninstalling pip-21.0.1:\n", + " Successfully uninstalled pip-21.0.1\n", + "Successfully installed pip-21.2.4\n", + "\u001B[36mINFO\u001B[0m[0006] Taking snapshot of full filesystem... \n", + "\u001B[36mINFO\u001B[0m[0006] RUN apt-get update && apt-get install -y --no-install-recommends git \n", + "\u001B[36mINFO\u001B[0m[0006] cmd: /bin/sh \n", + "\u001B[36mINFO\u001B[0m[0006] args: [-c apt-get update && apt-get install -y --no-install-recommends git] \n", + "\u001B[36mINFO\u001B[0m[0006] Running: [/bin/sh -c apt-get update && apt-get install -y --no-install-recommends git] \n", + "Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB]\n", + "Get:2 http://deb.debian.org/debian buster InRelease [122 kB]\n", + "Get:3 http://deb.debian.org/debian buster-updates InRelease [51.9 kB]\n", + "Get:4 http://security.debian.org/debian-security buster/updates/main amd64 Packages [314 kB]\n", + "Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7906 kB]\n", + "Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [8792 B]\n", + "Fetched 8468 kB in 2s (5347 kB/s)\n", + "Reading package lists...\n", + "Reading package lists...\n", + "Building dependency tree...\n", + "Reading state information...\n", + "The following additional packages will be installed:\n", + " git-man libcurl3-gnutls liberror-perl libgdbm-compat4 libgssapi-krb5-2\n", + " libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2\n", + " libldap-common libnghttp2-14 libpcre2-8-0 libperl5.28 libpsl5 librtmp1\n", + " libsasl2-2 libsasl2-modules-db libssh2-1 perl perl-modules-5.28\n", + "Suggested packages:\n", + " gettext-base git-daemon-run | git-daemon-sysvinit git-doc git-el git-email\n", + " git-gui gitk gitweb git-cvs git-mediawiki git-svn krb5-doc krb5-user\n", + " sensible-utils perl-doc libterm-readline-gnu-perl\n", + " | libterm-readline-perl-perl make libb-debug-perl liblocale-codes-perl\n", + "Recommended packages:\n", + " patch less ssh-client krb5-locales publicsuffix libsasl2-modules\n", + "The following NEW packages will be installed:\n", + " git git-man libcurl3-gnutls liberror-perl libgdbm-compat4 libgssapi-krb5-2\n", + " libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2\n", + " libldap-common libnghttp2-14 libpcre2-8-0 libperl5.28 libpsl5 librtmp1\n", + " libsasl2-2 libsasl2-modules-db libssh2-1 perl perl-modules-5.28\n", + "0 upgraded, 22 newly installed, 0 to remove and 16 not upgraded.\n", + "Need to get 16.4 MB of archives.\n", + "After this operation, 90.1 MB of additional disk space will be used.\n", + "Get:1 http://deb.debian.org/debian buster/main amd64 perl-modules-5.28 all 5.28.1-6+deb10u1 [2873 kB]\n", + "Get:2 http://deb.debian.org/debian buster/main amd64 libgdbm-compat4 amd64 1.18.1-4 [44.1 kB]\n", + "Get:3 http://deb.debian.org/debian buster/main amd64 libperl5.28 amd64 5.28.1-6+deb10u1 [3894 kB]\n", + "Get:4 http://deb.debian.org/debian buster/main amd64 perl amd64 5.28.1-6+deb10u1 [204 kB]\n", + "Get:5 http://deb.debian.org/debian buster/main amd64 libkeyutils1 amd64 1.6-6 [15.0 kB]\n", + "Get:6 http://deb.debian.org/debian buster/main amd64 libkrb5support0 amd64 1.17-3+deb10u3 [65.8 kB]\n", + "Get:7 http://deb.debian.org/debian buster/main amd64 libk5crypto3 amd64 1.17-3+deb10u3 [122 kB]\n", + "Get:8 http://deb.debian.org/debian buster/main amd64 libkrb5-3 amd64 1.17-3+deb10u3 [370 kB]\n", + "Get:9 http://deb.debian.org/debian buster/main amd64 libgssapi-krb5-2 amd64 1.17-3+deb10u3 [158 kB]\n", + "Get:10 http://deb.debian.org/debian buster/main amd64 libsasl2-modules-db amd64 2.1.27+dfsg-1+deb10u1 [69.1 kB]\n", + "Get:11 http://deb.debian.org/debian buster/main amd64 libsasl2-2 amd64 2.1.27+dfsg-1+deb10u1 [106 kB]\n", + "Get:12 http://deb.debian.org/debian buster/main amd64 libldap-common all 2.4.47+dfsg-3+deb10u6 [90.0 kB]\n", + "Get:13 http://deb.debian.org/debian buster/main amd64 libldap-2.4-2 amd64 2.4.47+dfsg-3+deb10u6 [224 kB]\n", + "Get:14 http://deb.debian.org/debian buster/main amd64 libnghttp2-14 amd64 1.36.0-2+deb10u1 [85.0 kB]\n", + "Get:15 http://deb.debian.org/debian buster/main amd64 libpsl5 amd64 0.20.2-2 [53.7 kB]\n", + "Get:16 http://deb.debian.org/debian buster/main amd64 librtmp1 amd64 2.4+20151223.gitfa8646d.1-2 [60.5 kB]\n", + "Get:17 http://deb.debian.org/debian buster/main amd64 libssh2-1 amd64 1.8.0-2.1 [140 kB]\n", + "Get:18 http://deb.debian.org/debian buster/main amd64 libcurl3-gnutls amd64 7.64.0-4+deb10u2 [330 kB]\n", + "Get:19 http://deb.debian.org/debian buster/main amd64 libpcre2-8-0 amd64 10.32-5 [213 kB]\n", + "Get:20 http://deb.debian.org/debian buster/main amd64 liberror-perl all 0.17027-2 [30.9 kB]\n", + "Get:21 http://deb.debian.org/debian buster/main amd64 git-man all 1:2.20.1-2+deb10u3 [1620 kB]\n", + "Get:22 http://deb.debian.org/debian buster/main amd64 git amd64 1:2.20.1-2+deb10u3 [5633 kB]\n", + "debconf: delaying package configuration, since apt-utils is not installed\n", + "Fetched 16.4 MB in 0s (62.3 MB/s)\n", + "Selecting previously unselected package perl-modules-5.28.\n", + "(Reading database ... 6840 files and directories currently installed.)\n", + "Preparing to unpack .../00-perl-modules-5.28_5.28.1-6+deb10u1_all.deb ...\n", + "Unpacking perl-modules-5.28 (5.28.1-6+deb10u1) ...\n", + "Selecting previously unselected package libgdbm-compat4:amd64.\n", + "Preparing to unpack .../01-libgdbm-compat4_1.18.1-4_amd64.deb ...\n", + "Unpacking libgdbm-compat4:amd64 (1.18.1-4) ...\n", + "Selecting previously unselected package libperl5.28:amd64.\n", + "Preparing to unpack .../02-libperl5.28_5.28.1-6+deb10u1_amd64.deb ...\n", + "Unpacking libperl5.28:amd64 (5.28.1-6+deb10u1) ...\n", + "Selecting previously unselected package perl.\n", + "Preparing to unpack .../03-perl_5.28.1-6+deb10u1_amd64.deb ...\n", + "Unpacking perl (5.28.1-6+deb10u1) ...\n", + "Selecting previously unselected package libkeyutils1:amd64.\n", + "Preparing to unpack .../04-libkeyutils1_1.6-6_amd64.deb ...\n", + "Unpacking libkeyutils1:amd64 (1.6-6) ...\n", + "Selecting previously unselected package libkrb5support0:amd64.\n", + "Preparing to unpack .../05-libkrb5support0_1.17-3+deb10u3_amd64.deb ...\n", + "Unpacking libkrb5support0:amd64 (1.17-3+deb10u3) ...\n", + "Selecting previously unselected package libk5crypto3:amd64.\n", + "Preparing to unpack .../06-libk5crypto3_1.17-3+deb10u3_amd64.deb ...\n", + "Unpacking libk5crypto3:amd64 (1.17-3+deb10u3) ...\n", + "Selecting previously unselected package libkrb5-3:amd64.\n", + "Preparing to unpack .../07-libkrb5-3_1.17-3+deb10u3_amd64.deb ...\n", + "Unpacking libkrb5-3:amd64 (1.17-3+deb10u3) ...\n", + "Selecting previously unselected package libgssapi-krb5-2:amd64.\n", + "Preparing to unpack .../08-libgssapi-krb5-2_1.17-3+deb10u3_amd64.deb ...\n", + "Unpacking libgssapi-krb5-2:amd64 (1.17-3+deb10u3) ...\n", + "Selecting previously unselected package libsasl2-modules-db:amd64.\n", + "Preparing to unpack .../09-libsasl2-modules-db_2.1.27+dfsg-1+deb10u1_amd64.deb ...\n", + "Unpacking libsasl2-modules-db:amd64 (2.1.27+dfsg-1+deb10u1) ...\n", + "Selecting previously unselected package libsasl2-2:amd64.\n", + "Preparing to unpack .../10-libsasl2-2_2.1.27+dfsg-1+deb10u1_amd64.deb ...\n", + "Unpacking libsasl2-2:amd64 (2.1.27+dfsg-1+deb10u1) ...\n", + "Selecting previously unselected package libldap-common.\n", + "Preparing to unpack .../11-libldap-common_2.4.47+dfsg-3+deb10u6_all.deb ...\n", + "Unpacking libldap-common (2.4.47+dfsg-3+deb10u6) ...\n", + "Selecting previously unselected package libldap-2.4-2:amd64.\n", + "Preparing to unpack .../12-libldap-2.4-2_2.4.47+dfsg-3+deb10u6_amd64.deb ...\n", + "Unpacking libldap-2.4-2:amd64 (2.4.47+dfsg-3+deb10u6) ...\n", + "Selecting previously unselected package libnghttp2-14:amd64.\n", + "Preparing to unpack .../13-libnghttp2-14_1.36.0-2+deb10u1_amd64.deb ...\n", + "Unpacking libnghttp2-14:amd64 (1.36.0-2+deb10u1) ...\n", + "Selecting previously unselected package libpsl5:amd64.\n", + "Preparing to unpack .../14-libpsl5_0.20.2-2_amd64.deb ...\n", + "Unpacking libpsl5:amd64 (0.20.2-2) ...\n", + "Selecting previously unselected package librtmp1:amd64.\n", + "Preparing to unpack .../15-librtmp1_2.4+20151223.gitfa8646d.1-2_amd64.deb ...\n", + "Unpacking librtmp1:amd64 (2.4+20151223.gitfa8646d.1-2) ...\n", + "Selecting previously unselected package libssh2-1:amd64.\n", + "Preparing to unpack .../16-libssh2-1_1.8.0-2.1_amd64.deb ...\n", + "Unpacking libssh2-1:amd64 (1.8.0-2.1) ...\n", + "Selecting previously unselected package libcurl3-gnutls:amd64.\n", + "Preparing to unpack .../17-libcurl3-gnutls_7.64.0-4+deb10u2_amd64.deb ...\n", + "Unpacking libcurl3-gnutls:amd64 (7.64.0-4+deb10u2) ...\n", + "Selecting previously unselected package libpcre2-8-0:amd64.\n", + "Preparing to unpack .../18-libpcre2-8-0_10.32-5_amd64.deb ...\n", + "Unpacking libpcre2-8-0:amd64 (10.32-5) ...\n", + "Selecting previously unselected package liberror-perl.\n", + "Preparing to unpack .../19-liberror-perl_0.17027-2_all.deb ...\n", + "Unpacking liberror-perl (0.17027-2) ...\n", + "Selecting previously unselected package git-man.\n", + "Preparing to unpack .../20-git-man_1%3a2.20.1-2+deb10u3_all.deb ...\n", + "Unpacking git-man (1:2.20.1-2+deb10u3) ...\n", + "Selecting previously unselected package git.\n", + "Preparing to unpack .../21-git_1%3a2.20.1-2+deb10u3_amd64.deb ...\n", + "Unpacking git (1:2.20.1-2+deb10u3) ...\n", + "Setting up perl-modules-5.28 (5.28.1-6+deb10u1) ...\n", + "Setting up libkeyutils1:amd64 (1.6-6) ...\n", + "Setting up libpsl5:amd64 (0.20.2-2) ...\n", + "Setting up libnghttp2-14:amd64 (1.36.0-2+deb10u1) ...\n", + "Setting up libldap-common (2.4.47+dfsg-3+deb10u6) ...\n", + "Setting up libkrb5support0:amd64 (1.17-3+deb10u3) ...\n", + "Setting up libsasl2-modules-db:amd64 (2.1.27+dfsg-1+deb10u1) ...\n", + "Setting up librtmp1:amd64 (2.4+20151223.gitfa8646d.1-2) ...\n", + "Setting up libgdbm-compat4:amd64 (1.18.1-4) ...\n", + "Setting up libpcre2-8-0:amd64 (10.32-5) ...\n", + "Setting up libk5crypto3:amd64 (1.17-3+deb10u3) ...\n", + "Setting up libsasl2-2:amd64 (2.1.27+dfsg-1+deb10u1) ...\n", + "Setting up libperl5.28:amd64 (5.28.1-6+deb10u1) ...\n", + "Setting up git-man (1:2.20.1-2+deb10u3) ...\n", + "Setting up libssh2-1:amd64 (1.8.0-2.1) ...\n", + "Setting up libkrb5-3:amd64 (1.17-3+deb10u3) ...\n", + "Setting up libldap-2.4-2:amd64 (2.4.47+dfsg-3+deb10u6) ...\n", + "Setting up perl (5.28.1-6+deb10u1) ...\n", + "Setting up libgssapi-krb5-2:amd64 (1.17-3+deb10u3) ...\n", + "Setting up libcurl3-gnutls:amd64 (7.64.0-4+deb10u2) ...\n", + "Setting up liberror-perl (0.17027-2) ...\n", + "Setting up git (1:2.20.1-2+deb10u3) ...\n", + "Processing triggers for libc-bin (2.28-10) ...\n", + "\u001B[36mINFO\u001B[0m[0012] Taking snapshot of full filesystem... \n", + "\u001B[36mINFO\u001B[0m[0015] RUN python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0 \n", + "\u001B[36mINFO\u001B[0m[0015] cmd: /bin/sh \n", + "\u001B[36mINFO\u001B[0m[0015] args: [-c python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0] \n", + "\u001B[36mINFO\u001B[0m[0015] Running: [/bin/sh -c python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0] \n", + "Collecting azureml-core==1.33.0\n", + " Downloading azureml_core-1.33.0-py3-none-any.whl (2.2 MB)\n", + "Collecting azureml-train-automl-client==1.33.0\n", + " Downloading azureml_train_automl_client-1.33.0-py3-none-any.whl (128 kB)\n", + "Collecting pytz\n", + " Downloading pytz-2021.3-py2.py3-none-any.whl (503 kB)\n", + "Collecting urllib3<=1.26.5,>=1.23\n", + " Downloading urllib3-1.26.5-py2.py3-none-any.whl (138 kB)\n", + "Collecting pathspec<1.0.0\n", + " Downloading pathspec-0.9.0-py2.py3-none-any.whl (31 kB)\n", + "Collecting azure-mgmt-containerregistry>=2.0.0\n", + " Downloading azure_mgmt_containerregistry-9.0.0-py3-none-any.whl (937 kB)\n", + "Collecting contextlib2<1.0.0\n", + " Downloading contextlib2-0.6.0.post1-py2.py3-none-any.whl (9.8 kB)\n", + "Collecting SecretStorage<4.0.0\n", + " Downloading SecretStorage-3.3.1-py3-none-any.whl (15 kB)\n", + "Collecting backports.tempfile\n", + " Downloading backports.tempfile-1.0-py2.py3-none-any.whl (4.4 kB)\n", + "Collecting docker<5.0.0\n", + " Downloading docker-4.4.4-py2.py3-none-any.whl (147 kB)\n", + "Collecting msrestazure<=0.6.4,>=0.4.33\n", + " Downloading msrestazure-0.6.4-py2.py3-none-any.whl (40 kB)\n", + "Collecting cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.*,<4.0.0\n", + " Downloading cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl (3.0 MB)\n", + "Collecting msrest<1.0.0,>=0.5.1\n", + " Downloading msrest-0.6.21-py2.py3-none-any.whl (85 kB)\n", + "Collecting requests<3.0.0,>=2.19.1\n", + " Downloading requests-2.27.1-py2.py3-none-any.whl (63 kB)\n", + "Collecting adal<=1.2.7,>=1.2.0\n", + " Downloading adal-1.2.7-py2.py3-none-any.whl (55 kB)\n", + "Collecting jsonpickle<3.0.0\n", + " Downloading jsonpickle-2.1.0-py2.py3-none-any.whl (38 kB)\n", + "Collecting azure-common<2.0.0,>=1.1.12\n", + " Downloading azure_common-1.1.27-py2.py3-none-any.whl (12 kB)\n", + "Collecting python-dateutil<3.0.0,>=2.7.3\n", + " Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)\n", + "Collecting PyJWT<3.0.0\n", + " Downloading PyJWT-2.3.0-py3-none-any.whl (16 kB)\n", + "Collecting azure-mgmt-authorization<1.0.0,>=0.40.0\n", + " Downloading azure_mgmt_authorization-0.61.0-py2.py3-none-any.whl (94 kB)\n", + "Collecting pyopenssl<21.0.0\n", + " Downloading pyOpenSSL-20.0.1-py2.py3-none-any.whl (54 kB)\n", + "Collecting jmespath<1.0.0\n", + " Downloading jmespath-0.10.0-py2.py3-none-any.whl (24 kB)\n", + "Collecting ndg-httpsclient<=0.5.1\n", + " Downloading ndg_httpsclient-0.5.1-py3-none-any.whl (34 kB)\n", + "Collecting azure-mgmt-storage<16.0.0,>=1.5.0\n", + " Downloading azure_mgmt_storage-11.2.0-py2.py3-none-any.whl (547 kB)\n", + "Collecting azure-mgmt-keyvault<10.0.0,>=0.40.0\n", + " Downloading azure_mgmt_keyvault-9.3.0-py2.py3-none-any.whl (412 kB)\n", + "Collecting ruamel.yaml<0.17.5,>=0.15.35\n", + " Downloading ruamel.yaml-0.17.4-py3-none-any.whl (101 kB)\n", + "Collecting azure-graphrbac<1.0.0,>=0.40.0\n", + " Downloading azure_graphrbac-0.61.1-py2.py3-none-any.whl (141 kB)\n", + "Collecting azure-mgmt-resource<15.0.0,>=1.2.1\n", + " Downloading azure_mgmt_resource-13.0.0-py2.py3-none-any.whl (1.3 MB)\n", + "Collecting azureml-dataset-runtime~=1.33.0\n", + " Downloading azureml_dataset_runtime-1.33.0-py3-none-any.whl (3.5 kB)\n", + "Collecting azureml-telemetry~=1.33.0\n", + " Downloading azureml_telemetry-1.33.0-py3-none-any.whl (30 kB)\n", + "Collecting azureml-automl-core~=1.33.0\n", + " Downloading azureml_automl_core-1.33.1-py3-none-any.whl (214 kB)\n", + "Collecting azure-mgmt-core<2.0.0,>=1.3.0\n", + " Downloading azure_mgmt_core-1.3.0-py2.py3-none-any.whl (25 kB)\n", + "Collecting azure-core<2.0.0,>=1.15.0\n", + " Downloading azure_core-1.21.1-py2.py3-none-any.whl (178 kB)\n", + "Collecting six>=1.11.0\n", + " Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)\n", + "Collecting pyarrow<4.0.0,>=0.17.0\n", + " Downloading pyarrow-3.0.0-cp37-cp37m-manylinux2014_x86_64.whl (20.7 MB)\n", + "Collecting azureml-dataprep<2.21.0a,>=2.20.0a\n", + " Downloading azureml_dataprep-2.20.1-py3-none-any.whl (39.4 MB)\n", + "Collecting numpy!=1.19.3\n", + " Downloading numpy-1.21.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.7 MB)\n", + "Collecting azure-identity<1.5.0,>=1.2.0\n", + " Downloading azure_identity-1.4.1-py2.py3-none-any.whl (86 kB)\n", + "Collecting dotnetcore2<3.0.0,>=2.1.14\n", + " Downloading dotnetcore2-2.1.23-py3-none-manylinux1_x86_64.whl (29.3 MB)\n", + "Collecting azureml-dataprep-native<39.0.0,>=38.0.0\n", + " Downloading azureml_dataprep_native-38.0.0-cp37-cp37m-manylinux1_x86_64.whl (1.3 MB)\n", + "Collecting azureml-dataprep-rslex<1.19.0a,>=1.18.0dev0\n", + " Downloading azureml_dataprep_rslex-1.18.2-cp37-cp37m-manylinux1_x86_64.whl (10.4 MB)\n", + "Collecting cloudpickle<2.0.0,>=1.1.0\n", + " Downloading cloudpickle-1.6.0-py3-none-any.whl (23 kB)\n", + "Collecting msal-extensions~=0.2.2\n", + " Downloading msal_extensions-0.2.2-py2.py3-none-any.whl (15 kB)\n", + "Collecting msal<2.0.0,>=1.3.0\n", + " Downloading msal-1.16.0-py2.py3-none-any.whl (78 kB)\n", + "Collecting applicationinsights\n", + " Downloading applicationinsights-0.11.10-py2.py3-none-any.whl (55 kB)\n", + "Collecting cffi>=1.12\n", + " Downloading cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (427 kB)\n", + "Collecting pycparser\n", + " Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)\n", + "Collecting websocket-client>=0.32.0\n", + " Downloading websocket_client-1.2.3-py3-none-any.whl (53 kB)\n", + "Collecting distro>=1.2.0\n", + " Downloading distro-1.6.0-py2.py3-none-any.whl (19 kB)\n", + "Collecting importlib-metadata\n", + " Downloading importlib_metadata-4.10.1-py3-none-any.whl (17 kB)\n", + "Collecting portalocker~=1.0\n", + " Downloading portalocker-1.7.1-py2.py3-none-any.whl (10 kB)\n", + "Collecting requests-oauthlib>=0.5.0\n", + " Downloading requests_oauthlib-1.3.1-py2.py3-none-any.whl (23 kB)\n", + "Collecting isodate>=0.6.0\n", + " Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)\n", + "Collecting certifi>=2017.4.17\n", + " Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)\n", + "Collecting pyasn1>=0.1.1\n", + " Downloading pyasn1-0.4.8-py2.py3-none-any.whl (77 kB)\n", + "Collecting charset-normalizer~=2.0.0\n", + " Downloading charset_normalizer-2.0.11-py3-none-any.whl (39 kB)\n", + "Collecting idna<4,>=2.5\n", + " Downloading idna-3.3-py3-none-any.whl (61 kB)\n", + "Collecting oauthlib>=3.0.0\n", + " Downloading oauthlib-3.2.0-py3-none-any.whl (151 kB)\n", + "Collecting ruamel.yaml.clib>=0.1.2\n", + " Downloading ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl (546 kB)\n", + "Collecting jeepney>=0.6\n", + " Downloading jeepney-0.7.1-py3-none-any.whl (54 kB)\n", + "Collecting backports.weakref\n", + " Downloading backports.weakref-1.0.post1-py2.py3-none-any.whl (5.2 kB)\n", + "Collecting zipp>=0.5\n", + " Downloading zipp-3.7.0-py3-none-any.whl (5.3 kB)\n", + "Collecting typing-extensions>=3.6.4\n", + " Downloading typing_extensions-4.0.1-py3-none-any.whl (22 kB)\n", + "Installing collected packages: pycparser, urllib3, idna, charset-normalizer, cffi, certifi, six, requests, PyJWT, oauthlib, cryptography, requests-oauthlib, python-dateutil, isodate, zipp, typing-extensions, portalocker, msrest, msal, azure-core, adal, websocket-client, ruamel.yaml.clib, pyopenssl, pyasn1, msrestazure, msal-extensions, jeepney, importlib-metadata, distro, backports.weakref, azure-mgmt-core, azure-common, SecretStorage, ruamel.yaml, pytz, pathspec, numpy, ndg-httpsclient, jsonpickle, jmespath, dotnetcore2, docker, contextlib2, cloudpickle, backports.tempfile, azureml-dataprep-rslex, azureml-dataprep-native, azure-mgmt-storage, azure-mgmt-resource, azure-mgmt-keyvault, azure-mgmt-containerregistry, azure-mgmt-authorization, azure-identity, azure-graphrbac, pyarrow, azureml-dataprep, azureml-core, applicationinsights, azureml-telemetry, azureml-dataset-runtime, azureml-automl-core, azureml-train-automl-client\n", + "Successfully installed PyJWT-2.3.0 SecretStorage-3.3.1 adal-1.2.7 applicationinsights-0.11.10 azure-common-1.1.27 azure-core-1.21.1 azure-graphrbac-0.61.1 azure-identity-1.4.1 azure-mgmt-authorization-0.61.0 azure-mgmt-containerregistry-9.0.0 azure-mgmt-core-1.3.0 azure-mgmt-keyvault-9.3.0 azure-mgmt-resource-13.0.0 azure-mgmt-storage-11.2.0 azureml-automl-core-1.33.1 azureml-core-1.33.0 azureml-dataprep-2.20.1 azureml-dataprep-native-38.0.0 azureml-dataprep-rslex-1.18.2 azureml-dataset-runtime-1.33.0 azureml-telemetry-1.33.0 azureml-train-automl-client-1.33.0 backports.tempfile-1.0 backports.weakref-1.0.post1 certifi-2021.10.8 cffi-1.15.0 charset-normalizer-2.0.11 cloudpickle-1.6.0 contextlib2-0.6.0.post1 cryptography-3.4.8 distro-1.6.0 docker-4.4.4 dotnetcore2-2.1.23 idna-3.3 importlib-metadata-4.10.1 isodate-0.6.1 jeepney-0.7.1 jmespath-0.10.0 jsonpickle-2.1.0 msal-1.16.0 msal-extensions-0.2.2 msrest-0.6.21 msrestazure-0.6.4 ndg-httpsclient-0.5.1 numpy-1.21.5 oauthlib-3.2.0 pathspec-0.9.0 portalocker-1.7.1 pyarrow-3.0.0 pyasn1-0.4.8 pycparser-2.21 pyopenssl-20.0.1 python-dateutil-2.8.2 pytz-2021.3 requests-2.27.1 requests-oauthlib-1.3.1 ruamel.yaml-0.17.4 ruamel.yaml.clib-0.2.6 six-1.16.0 typing-extensions-4.0.1 urllib3-1.26.5 websocket-client-1.2.3 zipp-3.7.0\n", + "WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\n", + "WARNING: You are using pip version 21.2.4; however, version 22.0.2 is available.\n", + "You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.\n", + "\u001B[36mINFO\u001B[0m[0040] Taking snapshot of full filesystem... \n", + "\u001B[36mINFO\u001B[0m[0059] RUN python -m pip install \"mlrun[complete] @ git+https://github.com/mlrun/mlrun@development\" \n", + "\u001B[36mINFO\u001B[0m[0059] cmd: /bin/sh \n", + "\u001B[36mINFO\u001B[0m[0059] args: [-c python -m pip install \"mlrun[complete] @ git+https://github.com/mlrun/mlrun@development\"] \n", + "\u001B[36mINFO\u001B[0m[0059] Running: [/bin/sh -c python -m pip install \"mlrun[complete] @ git+https://github.com/mlrun/mlrun@development\"] \n", + "Collecting mlrun[complete]@ git+https://github.com/mlrun/mlrun@development\n", + " Cloning https://github.com/mlrun/mlrun (to revision development) to /tmp/pip-install-yegk19ip/mlrun_b9fdf6ee5c8c4e2d9de3a37d0807b6c5\n", + " Running command git clone -q https://github.com/mlrun/mlrun /tmp/pip-install-yegk19ip/mlrun_b9fdf6ee5c8c4e2d9de3a37d0807b6c5\n", + " Resolved https://github.com/mlrun/mlrun to commit 832a07b11f3198b844d30b4a80db12a45c6e8948\n", + " Installing build dependencies: started\n", + " Installing build dependencies: finished with status 'done'\n", + " Getting requirements to build wheel: started\n", + " Getting requirements to build wheel: finished with status 'done'\n", + " Preparing wheel metadata: started\n", + " Preparing wheel metadata: finished with status 'done'\n", + "Collecting pymysql~=1.0\n", + " Downloading PyMySQL-1.0.2-py3-none-any.whl (43 kB)\n", + "Requirement already satisfied: pyarrow<6,>=1 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.0.0)\n", + "Collecting fsspec~=2021.8.1\n", + " Downloading fsspec-2021.8.1-py3-none-any.whl (119 kB)\n", + "Collecting v3io~=0.5.13\n", + " Downloading v3io-0.5.15-py3-none-any.whl (49 kB)\n", + "Collecting ipykernel~=5.0\n", + " Downloading ipykernel-5.5.6-py3-none-any.whl (121 kB)\n", + "Collecting click~=7.0\n", + " Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)\n", + "Collecting sqlalchemy~=1.3\n", + " Downloading SQLAlchemy-1.4.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.6 MB)\n", + "Collecting dask~=2021.11.2\n", + " Downloading dask-2021.11.2-py3-none-any.whl (1.0 MB)\n", + "Collecting distributed~=2021.11.2\n", + " Downloading distributed-2021.11.2-py3-none-any.whl (802 kB)\n", + "Collecting chardet<4.0,>=3.0.2\n", + " Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)\n", + "Collecting nuclio-jupyter~=0.8.22\n", + " Downloading nuclio_jupyter-0.8.22-py3-none-any.whl (49 kB)\n", + "Collecting pydantic~=1.5\n", + " Downloading pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.9 MB)\n", + "Collecting v3iofs~=0.1.7\n", + " Downloading v3iofs-0.1.10-py3-none-any.whl (13 kB)\n", + "Collecting kfp~=1.8.0\n", + " Downloading kfp-1.8.11.tar.gz (298 kB)\n", + "Collecting pandas~=1.2\n", + " Downloading pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.3 MB)\n", + "Collecting pyyaml~=5.1\n", + " Downloading PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl (636 kB)\n", + "Collecting orjson<3.4,>=3\n", + " Downloading orjson-3.3.1-cp37-cp37m-manylinux2014_x86_64.whl (208 kB)\n", + "Collecting inflection~=0.5.0\n", + " Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)\n", + "Collecting aiohttp~=3.8\n", + " Downloading aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)\n", + "Collecting humanfriendly~=8.2\n", + " Downloading humanfriendly-8.2-py2.py3-none-any.whl (86 kB)\n", + "Collecting ipython~=7.0\n", + " Downloading ipython-7.31.1-py3-none-any.whl (792 kB)\n", + "Collecting fastapi~=0.67.0\n", + " Downloading fastapi-0.67.0-py3-none-any.whl (51 kB)\n", + "Requirement already satisfied: requests~=2.22 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.27.1)\n", + "Collecting tabulate~=0.8.6\n", + " Downloading tabulate-0.8.9-py3-none-any.whl (25 kB)\n", + "Collecting nest-asyncio~=1.0\n", + " Downloading nest_asyncio-1.5.4-py3-none-any.whl (5.1 kB)\n", + "Collecting storey~=0.10.4\n", + " Downloading storey-0.10.4-py3-none-any.whl (116 kB)\n", + "Requirement already satisfied: urllib3<1.27,>=1.25.4 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.26.5)\n", + "Collecting GitPython~=3.0\n", + " Downloading GitPython-3.1.26-py3-none-any.whl (180 kB)\n", + "Collecting mergedeep~=1.3\n", + " Downloading mergedeep-1.3.4-py3-none-any.whl (6.4 kB)\n", + "Collecting semver~=2.13\n", + " Downloading semver-2.13.0-py2.py3-none-any.whl (12 kB)\n", + "Collecting cryptography<3.4,~=3.0\n", + " Downloading cryptography-3.3.2-cp36-abi3-manylinux2010_x86_64.whl (2.6 MB)\n", + "Collecting v3io-frames~=0.10.2\n", + " Downloading v3io_frames-0.10.2-py3-none-any.whl (35 kB)\n", + "Collecting deepdiff~=5.0\n", + " Downloading deepdiff-5.7.0-py3-none-any.whl (68 kB)\n", + "Requirement already satisfied: numpy<1.22.0,>=1.16.5 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.21.5)\n", + "Collecting python-dotenv~=0.17.0\n", + " Downloading python_dotenv-0.17.1-py2.py3-none-any.whl (18 kB)\n", + "Collecting alembic<1.6.0,~=1.4\n", + " Downloading alembic-1.5.8-py2.py3-none-any.whl (159 kB)\n", + "Collecting kubernetes~=12.0\n", + " Downloading kubernetes-12.0.1-py2.py3-none-any.whl (1.7 MB)\n", + "Collecting google-auth<2.0dev,>=1.25.0\n", + " Downloading google_auth-1.35.0-py2.py3-none-any.whl (152 kB)\n", + "Collecting azure-identity~=1.5\n", + " Downloading azure_identity-1.7.1-py2.py3-none-any.whl (129 kB)\n", + "Collecting aiobotocore~=1.4.0\n", + " Downloading aiobotocore-1.4.2.tar.gz (52 kB)\n", + "Collecting boto3<1.17.107,~=1.9\n", + " Downloading boto3-1.17.106-py2.py3-none-any.whl (131 kB)\n", + "Collecting azure-keyvault-secrets~=4.2\n", + " Downloading azure_keyvault_secrets-4.3.0-py2.py3-none-any.whl (233 kB)\n", + "Collecting s3fs~=2021.8.1\n", + " Downloading s3fs-2021.8.1-py3-none-any.whl (26 kB)\n", + "Collecting plotly~=5.4\n", + " Downloading plotly-5.5.0-py2.py3-none-any.whl (26.5 MB)\n", + "Collecting botocore<1.20.107,>=1.20.106\n", + " Downloading botocore-1.20.106-py2.py3-none-any.whl (7.7 MB)\n", + "Collecting adlfs~=2021.8.1\n", + " Downloading adlfs-2021.8.2.tar.gz (38 kB)\n", + "Collecting gcsfs~=2021.8.1\n", + " Downloading gcsfs-2021.8.1-py2.py3-none-any.whl (23 kB)\n", + "Collecting azure-storage-blob~=12.0\n", + " Downloading azure_storage_blob-12.9.0-py2.py3-none-any.whl (356 kB)\n", + "Requirement already satisfied: azure-core>=1.7.0 in /usr/local/lib/python3.7/site-packages (from adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.21.1)\n", + "Collecting azure-datalake-store<0.1,>=0.0.46\n", + " Downloading azure_datalake_store-0.0.52-py2.py3-none-any.whl (61 kB)\n", + "Collecting wrapt>=1.10.10\n", + " Downloading wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (79 kB)\n", + "Collecting aioitertools>=0.5.1\n", + " Downloading aioitertools-0.8.0-py3-none-any.whl (21 kB)\n", + "Collecting frozenlist>=1.1.1\n", + " Downloading frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (144 kB)\n", + "Collecting yarl<2.0,>=1.0\n", + " Downloading yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (271 kB)\n", + "Collecting multidict<7.0,>=4.5\n", + " Downloading multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (94 kB)\n", + "Collecting aiosignal>=1.1.2\n", + " Downloading aiosignal-1.2.0-py3-none-any.whl (8.2 kB)\n", + "Requirement already satisfied: typing-extensions>=3.7.4 in /usr/local/lib/python3.7/site-packages (from aiohttp~=3.8->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (4.0.1)\n", + "Collecting async-timeout<5.0,>=4.0.0a3\n", + " Downloading async_timeout-4.0.2-py3-none-any.whl (5.8 kB)\n", + "Collecting asynctest==0.13.0\n", + " Downloading asynctest-0.13.0-py3-none-any.whl (26 kB)\n", + "Collecting attrs>=17.3.0\n", + " Downloading attrs-21.4.0-py2.py3-none-any.whl (60 kB)\n", + "Requirement already satisfied: charset-normalizer<3.0,>=2.0 in /usr/local/lib/python3.7/site-packages (from aiohttp~=3.8->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.0.11)\n", + "Collecting Mako\n", + " Downloading Mako-1.1.6-py2.py3-none-any.whl (75 kB)\n", + "Collecting python-editor>=0.3\n", + " Downloading python_editor-1.0.4-py3-none-any.whl (4.9 kB)\n", + "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.7/site-packages (from alembic<1.6.0,~=1.4->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.8.2)\n", + "Requirement already satisfied: six>=1.11.0 in /usr/local/lib/python3.7/site-packages (from azure-core>=1.7.0->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.16.0)\n", + "Requirement already satisfied: cffi in /usr/local/lib/python3.7/site-packages (from azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.15.0)\n", + "Requirement already satisfied: adal>=0.4.2 in /usr/local/lib/python3.7/site-packages (from azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.2.7)\n", + "Requirement already satisfied: PyJWT<3,>=1.0.0 in /usr/local/lib/python3.7/site-packages (from adal>=0.4.2->azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.3.0)\n", + "Requirement already satisfied: msal<2.0.0,>=1.12.0 in /usr/local/lib/python3.7/site-packages (from azure-identity~=1.5->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.16.0)\n", + "Collecting msal-extensions~=0.3.0\n", + " Downloading msal_extensions-0.3.1-py2.py3-none-any.whl (18 kB)\n", + "Requirement already satisfied: azure-common~=1.1 in /usr/local/lib/python3.7/site-packages (from azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.1.27)\n", + "Requirement already satisfied: msrest>=0.6.21 in /usr/local/lib/python3.7/site-packages (from azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.6.21)\n", + "Requirement already satisfied: jmespath<1.0.0,>=0.7.1 in /usr/local/lib/python3.7/site-packages (from boto3<1.17.107,~=1.9->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.10.0)\n", + "Collecting s3transfer<0.5.0,>=0.4.0\n", + " Downloading s3transfer-0.4.2-py2.py3-none-any.whl (79 kB)\n", + "Requirement already satisfied: pycparser in /usr/local/lib/python3.7/site-packages (from cffi->azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.21)\n", + "Collecting partd>=0.3.10\n", + " Downloading partd-1.2.0-py3-none-any.whl (19 kB)\n", + "Collecting toolz>=0.8.2\n", + " Downloading toolz-0.11.2-py3-none-any.whl (55 kB)\n", + "Requirement already satisfied: cloudpickle>=1.1.1 in /usr/local/lib/python3.7/site-packages (from dask~=2021.11.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.6.0)\n", + "Collecting packaging>=20.0\n", + " Downloading packaging-21.3-py3-none-any.whl (40 kB)\n", + "Collecting ordered-set==4.0.2\n", + " Downloading ordered-set-4.0.2.tar.gz (10 kB)\n", + "Collecting tornado>=5\n", + " Downloading tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl (428 kB)\n", + "Collecting msgpack>=0.6.0\n", + " Downloading msgpack-1.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (299 kB)\n", + "Collecting zict>=0.1.3\n", + " Downloading zict-2.0.0-py3-none-any.whl (10 kB)\n", + "Requirement already satisfied: setuptools in /usr/local/lib/python3.7/site-packages (from distributed~=2021.11.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (53.0.0)\n", + "Collecting psutil>=5.0\n", + " Downloading psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (280 kB)\n", + "Collecting jinja2\n", + " Downloading Jinja2-3.0.3-py3-none-any.whl (133 kB)\n", + "Collecting tblib>=1.6.0\n", + " Downloading tblib-1.7.0-py2.py3-none-any.whl (12 kB)\n", + "Collecting sortedcontainers!=2.0.0,!=2.0.1\n", + " Downloading sortedcontainers-2.4.0-py2.py3-none-any.whl (29 kB)\n", + "Collecting starlette==0.14.2\n", + " Downloading starlette-0.14.2-py3-none-any.whl (60 kB)\n", + "Collecting decorator\n", + " Downloading decorator-5.1.1-py3-none-any.whl (9.1 kB)\n", + "Collecting google-auth-oauthlib\n", + " Downloading google_auth_oauthlib-0.4.6-py2.py3-none-any.whl (18 kB)\n", + "Collecting gitdb<5,>=4.0.1\n", + " Downloading gitdb-4.0.9-py3-none-any.whl (63 kB)\n", + "Collecting smmap<6,>=3.0.1\n", + " Downloading smmap-5.0.0-py3-none-any.whl (24 kB)\n", + "Collecting pyasn1-modules>=0.2.1\n", + " Downloading pyasn1_modules-0.2.8-py2.py3-none-any.whl (155 kB)\n", + "Collecting rsa<5,>=3.1.4\n", + " Downloading rsa-4.8-py3-none-any.whl (39 kB)\n", + "Collecting cachetools<5.0,>=2.0.0\n", + " Downloading cachetools-4.2.4-py3-none-any.whl (10 kB)\n", + "Collecting jupyter-client\n", + " Downloading jupyter_client-7.1.2-py3-none-any.whl (130 kB)\n", + "Collecting ipython-genutils\n", + " Downloading ipython_genutils-0.2.0-py2.py3-none-any.whl (26 kB)\n", + "Collecting traitlets>=4.1.0\n", + " Downloading traitlets-5.1.1-py3-none-any.whl (102 kB)\n", + "Collecting prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0\n", + " Downloading prompt_toolkit-3.0.26-py3-none-any.whl (375 kB)\n", + "Collecting jedi>=0.16\n", + " Downloading jedi-0.18.1-py2.py3-none-any.whl (1.6 MB)\n", + "Collecting matplotlib-inline\n", + " Downloading matplotlib_inline-0.1.3-py3-none-any.whl (8.2 kB)\n", + "Collecting pickleshare\n", + " Downloading pickleshare-0.7.5-py2.py3-none-any.whl (6.9 kB)\n", + "Collecting pygments\n", + " Downloading Pygments-2.11.2-py3-none-any.whl (1.1 MB)\n", + "Collecting backcall\n", + " Downloading backcall-0.2.0-py2.py3-none-any.whl (11 kB)\n", + "Collecting pexpect>4.3\n", + " Downloading pexpect-4.8.0-py2.py3-none-any.whl (59 kB)\n", + "Collecting parso<0.9.0,>=0.8.0\n", + " Downloading parso-0.8.3-py2.py3-none-any.whl (100 kB)\n", + "Collecting absl-py<2,>=0.9\n", + " Downloading absl_py-1.0.0-py3-none-any.whl (126 kB)\n", + "Collecting google-cloud-storage<2,>=1.20.0\n", + " Downloading google_cloud_storage-1.44.0-py2.py3-none-any.whl (106 kB)\n", + "Collecting google-api-python-client<2,>=1.7.8\n", + " Downloading google_api_python_client-1.12.10-py2.py3-none-any.whl (61 kB)\n", + "Collecting requests-toolbelt<1,>=0.8.0\n", + " Downloading requests_toolbelt-0.9.1-py2.py3-none-any.whl (54 kB)\n", + "Collecting cloudpickle>=1.1.1\n", + " Downloading cloudpickle-2.0.0-py3-none-any.whl (25 kB)\n", + "Collecting kfp-server-api<2.0.0,>=1.1.2\n", + " Downloading kfp-server-api-1.7.1.tar.gz (52 kB)\n", + "Collecting jsonschema<4,>=3.0.1\n", + " Downloading jsonschema-3.2.0-py2.py3-none-any.whl (56 kB)\n", + "Collecting Deprecated<2,>=1.2.7\n", + " Downloading Deprecated-1.2.13-py2.py3-none-any.whl (9.6 kB)\n", + "Collecting strip-hints<1,>=0.1.8\n", + " Downloading strip-hints-0.1.10.tar.gz (29 kB)\n", + "Collecting docstring-parser<1,>=0.7.3\n", + " Downloading docstring_parser-0.13.tar.gz (23 kB)\n", + " Installing build dependencies: started\n", + " Installing build dependencies: finished with status 'done'\n", + " Getting requirements to build wheel: started\n", + " Getting requirements to build wheel: finished with status 'done'\n", + " Preparing wheel metadata: started\n", + " Preparing wheel metadata: finished with status 'done'\n", + "Collecting kfp-pipeline-spec<0.2.0,>=0.1.13\n", + " Downloading kfp_pipeline_spec-0.1.13-py3-none-any.whl (18 kB)\n", + "Collecting fire<1,>=0.3.1\n", + " Downloading fire-0.4.0.tar.gz (87 kB)\n", + "Collecting protobuf<4,>=3.13.0\n", + " Downloading protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)\n", + "Collecting uritemplate<4,>=3.0.1\n", + " Downloading uritemplate-3.0.1-py2.py3-none-any.whl (15 kB)\n", + "Collecting typer<1.0,>=0.3.2\n", + " Downloading typer-0.4.0-py3-none-any.whl (27 kB)\n", + "Collecting typing-extensions>=3.7.4\n", + " Downloading typing_extensions-3.10.0.2-py3-none-any.whl (26 kB)\n", + "Collecting termcolor\n", + " Downloading termcolor-1.1.0.tar.gz (3.9 kB)\n", + "Collecting httplib2<1dev,>=0.15.0\n", + " Downloading httplib2-0.20.2-py3-none-any.whl (96 kB)\n", + "Collecting google-api-core<3dev,>=1.21.0\n", + " Downloading google_api_core-2.4.0-py2.py3-none-any.whl (111 kB)\n", + "Collecting google-auth-httplib2>=0.0.3\n", + " Downloading google_auth_httplib2-0.1.0-py2.py3-none-any.whl (9.3 kB)\n", + "Collecting googleapis-common-protos<2.0dev,>=1.52.0\n", + " Downloading googleapis_common_protos-1.54.0-py2.py3-none-any.whl (207 kB)\n", + "Collecting google-cloud-core<3.0dev,>=1.6.0\n", + " Downloading google_cloud_core-2.2.2-py2.py3-none-any.whl (29 kB)\n", + "Collecting google-resumable-media<3.0dev,>=1.3.0\n", + " Downloading google_resumable_media-2.1.0-py2.py3-none-any.whl (75 kB)\n", + "Collecting google-crc32c<2.0dev,>=1.0\n", + " Downloading google_crc32c-1.3.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38 kB)\n", + "Collecting pyparsing!=3.0.0,!=3.0.1,!=3.0.2,!=3.0.3,<4,>=2.4.2\n", + " Downloading pyparsing-3.0.7-py3-none-any.whl (98 kB)\n", + "Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/site-packages (from jsonschema<4,>=3.0.1->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (4.10.1)\n", + "Collecting pyrsistent>=0.14.0\n", + " Downloading pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (117 kB)\n", + "Requirement already satisfied: certifi in /usr/local/lib/python3.7/site-packages (from kfp-server-api<2.0.0,>=1.1.2->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2021.10.8)\n", + "Requirement already satisfied: requests-oauthlib in /usr/local/lib/python3.7/site-packages (from kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.3.1)\n", + "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /usr/local/lib/python3.7/site-packages (from kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.2.3)\n", + "Requirement already satisfied: portalocker<3,>=1.0 in /usr/local/lib/python3.7/site-packages (from msal-extensions~=0.3.0->azure-identity~=1.5->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.7.1)\n", + "Requirement already satisfied: isodate>=0.6.0 in /usr/local/lib/python3.7/site-packages (from msrest>=0.6.21->azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.6.1)\n", + "Collecting notebook>=5.2.0\n", + " Downloading notebook-6.4.8-py3-none-any.whl (9.9 MB)\n", + "Collecting nbconvert>=5.4\n", + " Downloading nbconvert-6.4.1-py3-none-any.whl (557 kB)\n", + "Collecting nbformat>=4.4\n", + " Downloading nbformat-5.1.3-py3-none-any.whl (178 kB)\n", + "Collecting mistune<2,>=0.8.1\n", + " Downloading mistune-0.8.4-py2.py3-none-any.whl (16 kB)\n", + "Collecting defusedxml\n", + " Downloading defusedxml-0.7.1-py2.py3-none-any.whl (25 kB)\n", + "Collecting nbclient<0.6.0,>=0.5.0\n", + " Downloading nbclient-0.5.10-py3-none-any.whl (69 kB)\n", + "Collecting jupyter-core\n", + " Downloading jupyter_core-4.9.1-py3-none-any.whl (86 kB)\n", + "Collecting testpath\n", + " Downloading testpath-0.5.0-py3-none-any.whl (84 kB)\n", + "Collecting pandocfilters>=1.4.1\n", + " Downloading pandocfilters-1.5.0-py2.py3-none-any.whl (8.7 kB)\n", + "Collecting bleach\n", + " Downloading bleach-4.1.0-py2.py3-none-any.whl (157 kB)\n", + "Collecting jupyterlab-pygments\n", + " Downloading jupyterlab_pygments-0.1.2-py2.py3-none-any.whl (4.6 kB)\n", + "Collecting entrypoints>=0.2.2\n", + " Downloading entrypoints-0.3-py2.py3-none-any.whl (11 kB)\n", + "Collecting MarkupSafe>=2.0\n", + " Downloading MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (31 kB)\n", + "Collecting pyzmq>=13\n", + " Downloading pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.1 MB)\n", + "Collecting terminado>=0.8.3\n", + " Downloading terminado-0.13.1-py3-none-any.whl (14 kB)\n", + "Collecting argon2-cffi\n", + " Downloading argon2_cffi-21.3.0-py3-none-any.whl (14 kB)\n", + "Collecting Send2Trash>=1.8.0\n", + " Downloading Send2Trash-1.8.0-py3-none-any.whl (18 kB)\n", + "Collecting prometheus-client\n", + " Downloading prometheus_client-0.13.1-py3-none-any.whl (57 kB)\n", + "Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/site-packages (from pandas~=1.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2021.3)\n", + "Collecting locket\n", + " Downloading locket-0.2.1-py2.py3-none-any.whl (4.1 kB)\n", + "Collecting ptyprocess>=0.5\n", + " Downloading ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)\n", + "Collecting tenacity>=6.2.0\n", + " Downloading tenacity-8.0.1-py3-none-any.whl (24 kB)\n", + "Collecting wcwidth\n", + " Downloading wcwidth-0.2.5-py2.py3-none-any.whl (30 kB)\n", + "Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/site-packages (from pyasn1-modules>=0.2.1->google-auth<2.0dev,>=1.25.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.4.8)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.7/site-packages (from requests~=2.22->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.3)\n", + "Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/site-packages (from requests-oauthlib->kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.2.0)\n", + "Collecting greenlet!=0.4.17\n", + " Downloading greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (150 kB)\n", + "Collecting grpcio-tools<1.42,>1.34.0\n", + " Downloading grpcio_tools-1.41.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.4 MB)\n", + "Collecting grpcio<1.42,>1.34.0\n", + " Downloading grpcio-1.41.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.9 MB)\n", + "Requirement already satisfied: wheel in /usr/local/lib/python3.7/site-packages (from strip-hints<1,>=0.1.8->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.36.2)\n", + "Collecting ujson>=3.0.0\n", + " Downloading ujson-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (43 kB)\n", + "Collecting future>=0.18.2\n", + " Downloading future-0.18.2.tar.gz (829 kB)\n", + "Collecting heapdict\n", + " Downloading HeapDict-1.0.1-py3-none-any.whl (3.9 kB)\n", + "Collecting argon2-cffi-bindings\n", + " Downloading argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (86 kB)\n", + "Collecting webencodings\n", + " Downloading webencodings-0.5.1-py2.py3-none-any.whl (11 kB)\n", + "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/site-packages (from importlib-metadata->jsonschema<4,>=3.0.1->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.7.0)\n", + "Building wheels for collected packages: adlfs, aiobotocore, ordered-set, kfp, docstring-parser, fire, kfp-server-api, strip-hints, future, mlrun, termcolor\n", + " Building wheel for adlfs (setup.py): started\n", + " Building wheel for adlfs (setup.py): finished with status 'done'\n", + " Created wheel for adlfs: filename=adlfs-2021.8.2-py3-none-any.whl size=21466 sha256=d3076fc23e1d05f49958ba450b9913a80c23c755d3347327d1d2150656fa3185\n", + " Stored in directory: /root/.cache/pip/wheels/0d/88/1d/e06072abb7fb4d59b5cf94e194e53017dfa2dc47af4dec88b7\n", + " Building wheel for aiobotocore (setup.py): started\n", + " Building wheel for aiobotocore (setup.py): finished with status 'done'\n", + " Created wheel for aiobotocore: filename=aiobotocore-1.4.2-py3-none-any.whl size=49910 sha256=d630bfe25d72229a76e207cb2de8dd29839368673c27edc12229b314660a7e69\n", + " Stored in directory: /root/.cache/pip/wheels/33/e7/d9/b297a9aa9c43d56bc2463e6e2771655ff638f30b30f0b61fcb\n", + " Building wheel for ordered-set (setup.py): started\n", + " Building wheel for ordered-set (setup.py): finished with status 'done'\n", + " Created wheel for ordered-set: filename=ordered_set-4.0.2-py2.py3-none-any.whl size=8210 sha256=d0a6fcb9c69107866ca516cb90c3a6a1c0dd00c2f55e981d69b476e92fe85c0a\n", + " Stored in directory: /root/.cache/pip/wheels/73/2b/f6/26e9f84153c25050fe7c09e88f8e32a6be3c7034a38c418319\n", + " Building wheel for kfp (setup.py): started\n", + " Building wheel for kfp (setup.py): finished with status 'done'\n", + " Created wheel for kfp: filename=kfp-1.8.11-py3-none-any.whl size=414450 sha256=62bc86dbc4fbb6d431756182a297f87d5d9f08edfb8c2bab347b81fe0654cad3\n", + " Stored in directory: /root/.cache/pip/wheels/85/1e/ee/a14b49663bddf9e72d1c269cbe53970167bfabb53cadbbea3a\n", + " Building wheel for docstring-parser (PEP 517): started\n", + " Building wheel for docstring-parser (PEP 517): finished with status 'done'\n", + " Created wheel for docstring-parser: filename=docstring_parser-0.13-py3-none-any.whl size=31866 sha256=6ab7172ddcc24d27d93d31af6438a00053464e76585907654ec90dfb3ecf1886\n", + " Stored in directory: /root/.cache/pip/wheels/bd/88/3c/d1aa049309f7945178cac9fbe6561a86424f432da57c18ca0f\n", + " Building wheel for fire (setup.py): started\n", + " Building wheel for fire (setup.py): finished with status 'done'\n", + " Created wheel for fire: filename=fire-0.4.0-py2.py3-none-any.whl size=115928 sha256=6704c4bed4908d06fbe2e61f97718607c4ef9dee5a573d09acf9882e6e620757\n", + " Stored in directory: /root/.cache/pip/wheels/8a/67/fb/2e8a12fa16661b9d5af1f654bd199366799740a85c64981226\n", + " Building wheel for kfp-server-api (setup.py): started\n", + " Building wheel for kfp-server-api (setup.py): finished with status 'done'\n", + " Created wheel for kfp-server-api: filename=kfp_server_api-1.7.1-py3-none-any.whl size=92618 sha256=b156a487ea471572b7c0d0dc85826bb5f6554ce5d097c3d16cdd75e36a93100a\n", + " Stored in directory: /root/.cache/pip/wheels/68/3f/d5/734c0278dd6c8969cef359edcf059505a61452c5eb0e2760e1\n", + " Building wheel for strip-hints (setup.py): started\n", + " Building wheel for strip-hints (setup.py): finished with status 'done'\n", + " Created wheel for strip-hints: filename=strip_hints-0.1.10-py2.py3-none-any.whl size=22279 sha256=ebdf8455bb18636c3ffe99dc1ffe7262298094e000e46a15df3ef1b809e3770f\n", + " Stored in directory: /root/.cache/pip/wheels/5e/14/c3/6e44e9b2545f2d570b03f5b6d38c00b7534aa8abb376978363\n", + " Building wheel for future (setup.py): started\n", + " Building wheel for future (setup.py): finished with status 'done'\n", + " Created wheel for future: filename=future-0.18.2-py3-none-any.whl size=491059 sha256=b71c260b8cae9faa06e701eb03743481d68f427d7ed0886bddf8eec6fab17927\n", + " Stored in directory: /root/.cache/pip/wheels/56/b0/fe/4410d17b32f1f0c3cf54cdfb2bc04d7b4b8f4ae377e2229ba0\n", + " Building wheel for mlrun (PEP 517): started\n", + " Building wheel for mlrun (PEP 517): finished with status 'done'\n", + " Created wheel for mlrun: filename=mlrun-0.0.0+unstable-py3-none-any.whl size=799835 sha256=a37564dbf60ba19531146c0404fa02216cea4492894cbafd9738bb199ce45775\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-xz3ex0pi/wheels/cd/42/82/13965317128ea26acc3fb21b24cc254077454998599db6f161\n", + " Building wheel for termcolor (setup.py): started\n", + " Building wheel for termcolor (setup.py): finished with status 'done'\n", + " Created wheel for termcolor: filename=termcolor-1.1.0-py3-none-any.whl size=4829 sha256=09ed568b0b6ea586b107bc3decf5a736c95331052246548b5a560c69a88e9414\n", + " Stored in directory: /root/.cache/pip/wheels/3f/e3/ec/8a8336ff196023622fbcb36de0c5a5c218cbb24111d1d4c7f2\n", + "Successfully built adlfs aiobotocore ordered-set kfp docstring-parser fire kfp-server-api strip-hints future mlrun termcolor\n", + "Installing collected packages: typing-extensions, traitlets, pyrsistent, attrs, wcwidth, tornado, rsa, pyzmq, pyparsing, pyasn1-modules, ptyprocess, protobuf, parso, nest-asyncio, jupyter-core, jsonschema, ipython-genutils, entrypoints, cachetools, webencodings, pygments, prompt-toolkit, pickleshare, pexpect, packaging, nbformat, matplotlib-inline, MarkupSafe, jupyter-client, jedi, googleapis-common-protos, google-auth, decorator, cryptography, backcall, ujson, toolz, testpath, pandocfilters, nbclient, multidict, mistune, locket, jupyterlab-pygments, jinja2, ipython, httplib2, grpcio, google-crc32c, google-api-core, future, frozenlist, defusedxml, botocore, bleach, argon2-cffi-bindings, yarl, wrapt, v3io, uritemplate, terminado, termcolor, smmap, Send2Trash, s3transfer, pyyaml, prometheus-client, partd, pandas, nbconvert, ipykernel, heapdict, grpcio-tools, greenlet, google-resumable-media, google-cloud-core, google-auth-httplib2, fsspec, cloudpickle, click, asynctest, async-timeout, argon2-cffi, aiosignal, zict, v3iofs, v3io-frames, typer, tblib, tabulate, strip-hints, starlette, sqlalchemy, sortedcontainers, requests-toolbelt, python-editor, pydantic, psutil, ordered-set, notebook, msgpack, msal-extensions, Mako, kubernetes, kfp-server-api, kfp-pipeline-spec, google-cloud-storage, google-api-python-client, gitdb, fire, docstring-parser, Deprecated, dask, boto3, aioitertools, aiohttp, absl-py, tenacity, storey, semver, python-dotenv, pymysql, orjson, nuclio-jupyter, mergedeep, kfp, inflection, humanfriendly, google-auth-oauthlib, GitPython, fastapi, distributed, deepdiff, chardet, azure-storage-blob, azure-identity, azure-datalake-store, alembic, aiobotocore, s3fs, plotly, mlrun, gcsfs, azure-keyvault-secrets, adlfs\n", + " Attempting uninstall: typing-extensions\n", + " Found existing installation: typing-extensions 4.0.1\n", + " Uninstalling typing-extensions-4.0.1:\n", + " Successfully uninstalled typing-extensions-4.0.1\n", + " Attempting uninstall: cryptography\n", + " Found existing installation: cryptography 3.4.8\n", + " Uninstalling cryptography-3.4.8:\n", + " Successfully uninstalled cryptography-3.4.8\n", + " Attempting uninstall: cloudpickle\n", + " Found existing installation: cloudpickle 1.6.0\n", + " Uninstalling cloudpickle-1.6.0:\n", + " Successfully uninstalled cloudpickle-1.6.0\n", + " Attempting uninstall: msal-extensions\n", + " Found existing installation: msal-extensions 0.2.2\n", + " Uninstalling msal-extensions-0.2.2:\n", + " Successfully uninstalled msal-extensions-0.2.2\n", + " Attempting uninstall: azure-identity\n", + " Found existing installation: azure-identity 1.4.1\n", + " Uninstalling azure-identity-1.4.1:\n", + " Successfully uninstalled azure-identity-1.4.1\n", + "ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "azureml-dataprep 2.20.1 requires azure-identity<1.5.0,>=1.2.0, but you have azure-identity 1.7.1 which is incompatible.\n", + "azureml-dataprep 2.20.1 requires cloudpickle<2.0.0,>=1.1.0, but you have cloudpickle 2.0.0 which is incompatible.\n", + "Successfully installed Deprecated-1.2.13 GitPython-3.1.26 Mako-1.1.6 MarkupSafe-2.0.1 Send2Trash-1.8.0 absl-py-1.0.0 adlfs-2021.8.2 aiobotocore-1.4.2 aiohttp-3.8.1 aioitertools-0.8.0 aiosignal-1.2.0 alembic-1.5.8 argon2-cffi-21.3.0 argon2-cffi-bindings-21.2.0 async-timeout-4.0.2 asynctest-0.13.0 attrs-21.4.0 azure-datalake-store-0.0.52 azure-identity-1.7.1 azure-keyvault-secrets-4.3.0 azure-storage-blob-12.9.0 backcall-0.2.0 bleach-4.1.0 boto3-1.17.106 botocore-1.20.106 cachetools-4.2.4 chardet-3.0.4 click-7.1.2 cloudpickle-2.0.0 cryptography-3.3.2 dask-2021.11.2 decorator-5.1.1 deepdiff-5.7.0 defusedxml-0.7.1 distributed-2021.11.2 docstring-parser-0.13 entrypoints-0.3 fastapi-0.67.0 fire-0.4.0 frozenlist-1.3.0 fsspec-2021.8.1 future-0.18.2 gcsfs-2021.8.1 gitdb-4.0.9 google-api-core-2.4.0 google-api-python-client-1.12.10 google-auth-1.35.0 google-auth-httplib2-0.1.0 google-auth-oauthlib-0.4.6 google-cloud-core-2.2.2 google-cloud-storage-1.44.0 google-crc32c-1.3.0 google-resumable-media-2.1.0 googleapis-common-protos-1.54.0 greenlet-1.1.2 grpcio-1.41.1 grpcio-tools-1.41.1 heapdict-1.0.1 httplib2-0.20.2 humanfriendly-8.2 inflection-0.5.1 ipykernel-5.5.6 ipython-7.31.1 ipython-genutils-0.2.0 jedi-0.18.1 jinja2-3.0.3 jsonschema-3.2.0 jupyter-client-7.1.2 jupyter-core-4.9.1 jupyterlab-pygments-0.1.2 kfp-1.8.11 kfp-pipeline-spec-0.1.13 kfp-server-api-1.7.1 kubernetes-12.0.1 locket-0.2.1 matplotlib-inline-0.1.3 mergedeep-1.3.4 mistune-0.8.4 mlrun-0.0.0+unstable msal-extensions-0.3.1 msgpack-1.0.3 multidict-6.0.2 nbclient-0.5.10 nbconvert-6.4.1 nbformat-5.1.3 nest-asyncio-1.5.4 notebook-6.4.8 nuclio-jupyter-0.8.22 ordered-set-4.0.2 orjson-3.3.1 packaging-21.3 pandas-1.3.5 pandocfilters-1.5.0 parso-0.8.3 partd-1.2.0 pexpect-4.8.0 pickleshare-0.7.5 plotly-5.5.0 prometheus-client-0.13.1 prompt-toolkit-3.0.26 protobuf-3.19.4 psutil-5.9.0 ptyprocess-0.7.0 pyasn1-modules-0.2.8 pydantic-1.9.0 pygments-2.11.2 pymysql-1.0.2 pyparsing-3.0.7 pyrsistent-0.18.1 python-dotenv-0.17.1 python-editor-1.0.4 pyyaml-5.4.1 pyzmq-22.3.0 requests-toolbelt-0.9.1 rsa-4.8 s3fs-2021.8.1 s3transfer-0.4.2 semver-2.13.0 smmap-5.0.0 sortedcontainers-2.4.0 sqlalchemy-1.4.31 starlette-0.14.2 storey-0.10.4 strip-hints-0.1.10 tabulate-0.8.9 tblib-1.7.0 tenacity-8.0.1 termcolor-1.1.0 terminado-0.13.1 testpath-0.5.0 toolz-0.11.2 tornado-6.1 traitlets-5.1.1 typer-0.4.0 typing-extensions-3.10.0.2 ujson-5.1.0 uritemplate-3.0.1 v3io-0.5.15 v3io-frames-0.10.2 v3iofs-0.1.10 wcwidth-0.2.5 webencodings-0.5.1 wrapt-1.13.3 yarl-1.7.2 zict-2.0.0\n", + "WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\n", + "WARNING: You are using pip version 21.2.4; however, version 22.0.2 is available.\n", + "You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.\n", + "\u001B[36mINFO\u001B[0m[0128] Taking snapshot of full filesystem... \n", + "\u001B[36mINFO\u001B[0m[0148] Pushing image to docker-registry.default-tenant.app.yh41.iguazio-cd1.com/mlrun/func-azureml-yonatan-azureml-utils:latest \n", + "\u001B[36mINFO\u001B[0m[0153] Pushed image to 1 destinations \n", + "> 2022-02-02 18:30:56,789 [info] starting run azureml-utils-train uid=48dbbe26a2a34b5baaec5ca8aba3de5e DB=http://mlrun-api:8080\n", + "> 2022-02-02 18:30:56,988 [info] Job is running in the background, pod: azureml-utils-train-7pp86\n", + "> 2022-02-02 18:31:30,311 [warning] Failed resolving version info. Ignoring and using defaults\n", + "> 2022-02-02 18:31:32,893 [warning] Server or client version is unstable. Assuming compatible: {'server_version': '0.0.0+unstable', 'client_version': '0.0.0+unstable'}\n", + "Failure while loading azureml_run_type_providers. Failed to load entrypoint automl = azureml.train.automl.run:AutoMLRun._from_run_dto with exception (cloudpickle 2.0.0 (/usr/local/lib/python3.7/site-packages), Requirement.parse('cloudpickle<2.0.0,>=1.1.0'), {'azureml-dataprep'}).\n", + "> 2022-02-02 18:31:34,680 [info] Loading AzureML Workspace\n", + "> 2022-02-02 18:31:36,956 [info] Initializing AzureML experiment azure-automl-test\n", + "> 2022-02-02 18:31:40,005 [info] Initializing AzureML compute target azureml-cpu\n", + "> 2022-02-02 18:31:40,206 [info] Found existing cluster, will use it.\n", + "Succeeded\n", + "AmlCompute wait for completion finished\n", + "\n", + "Minimum number of nodes requested have been provisioned\n", + "> 2022-02-02 18:31:40,322 [info] Connecting to AzureML experiment default datastore\n", + "> 2022-02-02 18:31:41,624 [info] Retrieving feature vector and uploading to Azure blob storage: az://azureml-blobstore-27f8977b-4946-4ca0-bdc5-5a685d2fe8d7/iris.csv\n", + "> 2022-02-02 18:31:41,912 [info] Registering dataset iris in Azure ML\n", + "> 2022-02-02 18:31:41,912 [info] OpenSSL version must be 1.1. Overriding the OpenSSL version to 1.1\n", + "> 2022-02-02 18:31:49,558 [info] Setting up experiment parameters\n", + "> 2022-02-02 18:31:49,812 [info] Submitting and running experiment\n", + "Submitting remote run.\n", + "Parent Run ID: AutoML_35c51a81-98fd-44fb-aa23-3192c3aca08d\n", + "https://ml.azure.com/runs/AutoML_35c51a81-98fd-44fb-aa23-3192c3aca08d?wsid=/subscriptions/8d81bc0b-6abd-4395-be83-000251d9fdbe/resourcegroups/nick/workspaces/NickAzureML&tid=af053911-a8b7-450d-9f58-0c08567d4769\n", + "\n", + "Current status: ModelSelection. Beginning model selection.\n", + "\n", + "****************************************************************************************************\n", + "DATA GUARDRAILS: \n", + "\n", + "TYPE: Class balancing detection\n", + "STATUS: PASSED\n", + "DESCRIPTION: Your inputs were analyzed, and all classes are balanced in your training data.\n", + " Learn more about imbalanced data: https://aka.ms/AutomatedMLImbalancedData\n", + "\n", + "****************************************************************************************************\n", + "\n", + "****************************************************************************************************\n", + "ITERATION: The iteration being evaluated.\n", + "PIPELINE: A summary description of the pipeline being evaluated.\n", + "DURATION: Time taken for the current iteration.\n", + "METRIC: The result of computing score on the fitted pipeline.\n", + "BEST: The best observed score thus far.\n", + "****************************************************************************************************\n", + "\n", + " ITERATION PIPELINE DURATION METRIC BEST\n", + " 0 RobustScaler LogisticRegression 0:00:21 0.9667 0.9667\n", + " 1 StandardScalerWrapper SVM 0:00:18 0.9533 0.9667\n", + " 2 StandardScalerWrapper LogisticRegression 0:00:22 0.9733 0.9733\n", + " 3 MaxAbsScaler LogisticRegression 0:00:22 0.9533 0.9733\n", + " 4 MaxAbsScaler LogisticRegression 0:00:21 0.9733 0.9733\n", + "\n", + "********************************************************************************************\n", + "\n", + "> 2022-02-02 18:38:28,144 [info] Registering model\n", + "> 2022-02-02 18:38:29,495 [info] Registered model with name 'iris-model', id 'iris-model:178', version '178'\n", + "> 2022-02-02 18:38:29,495 [info] Downloading model iris-model:178\n", + "> 2022-02-02 18:38:34,083 [info] Logging model_1_standardscaler_logisticregression model to MLRun\n", + "> 2022-02-02 18:38:34,621 [info] Registering model\n", + "> 2022-02-02 18:38:35,519 [info] Registered model with name 'iris-model', id 'iris-model:179', version '179'\n", + "> 2022-02-02 18:38:35,519 [info] Downloading model iris-model:179\n", + "> 2022-02-02 18:38:39,972 [info] Logging model_2_maxabsscaler_logisticregression model to MLRun\n", + "> 2022-02-02 18:38:40,317 [info] Registering model\n", + "> 2022-02-02 18:38:41,087 [info] Registered model with name 'iris-model', id 'iris-model:180', version '180'\n", + "> 2022-02-02 18:38:41,087 [info] Downloading model iris-model:180\n", + "> 2022-02-02 18:38:46,299 [info] Logging model_3_robustscaler_logisticregression model to MLRun\n", + "> 2022-02-02 18:38:47,615 [info] run executed, status=completed\n", + "final state: completed\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
azureml-yonatan0Feb 02 18:31:32completedazureml-utils-train
v3io_user=yonatan
kind=job
owner=yonatan
host=azureml-utils-train-7pp86
dataset
experiment_name=azure-automl-test
cpu_cluster_name=azureml-cpu
dataset_name=iris
dataset_description=iris training data
label_column_name=label
create_new_version=True
register_model_name=iris-model
save_n_models=3
automl_settings={'task': 'classification', 'debug_log': 'automl_errors.log', 'enable_early_stopping': False, 'allowed_models': ['LogisticRegression', 'SGD', 'SVM'], 'iterations': 5, 'iteration_timeout_minutes': 2, 'max_concurrent_iterations': 2, 'max_cores_per_iteration': -1, 'n_cross_validations': 5, 'primary_metric': 'accuracy', 'featurization': 'off', 'model_explainability': False, 'enable_voting_ensemble': False, 'enable_stack_ensemble': False}
dataset_blob_path=az://azureml-blobstore-27f8977b-4946-4ca0-bdc5-5a685d2fe8d7/iris.csv
best_iteration=1
auc_macro=0.9973298059964726
auc_micro=0.9979999999999999
norm_macro_recall=0.9594444444444443
balanced_accuracy=0.9729629629629629
f1_score_macro=0.9721779225097302
weighted_accuracy=0.9739694654594448
average_precision_score_weighted=0.9953861693861693
f1_score_weighted=0.9730901151988108
precision_score_micro=0.9733333333333334
matthews_correlation=0.9613232982405628
recall_score_macro=0.9729629629629629
precision_score_weighted=0.9767380952380952
recall_score_micro=0.9733333333333334
precision_score_macro=0.9754761904761905
average_precision_score_macro=0.9954059829059829
accuracy=0.9733333333333334
auc_weighted=0.9972857142857142
recall_score_weighted=0.9733333333333334
f1_score_micro=0.9733333333333334
average_precision_score_micro=0.9962096994520057
log_loss=0.07548089806904337
model
iteration_results
parallel_coordinates
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-02-02 18:38:48,608 [info] run executed, status=completed\n" + ] + } + ], + "source": [ + "azureml_run = azureml_fn.run(\n", + " handler=\"train\",\n", + " inputs={\"dataset\": DATA_URL},\n", + " params=params,\n", + ")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "View the run result: (more details in the UI)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
stateiterparam.data_trans_class_nameparam.data_trans_moduleparam.data_trans_spec_classparam.train_class_nameparam.train_moduleparam.train_param_kwargs_Cparam.train_param_kwargs_class_weightparam.train_spec_class...output.precision_score_weightedoutput.recall_score_microoutput.precision_score_macrooutput.average_precision_score_macrooutput.accuracyoutput.auc_weightedoutput.recall_score_weightedoutput.f1_score_microoutput.average_precision_score_microoutput.log_loss
0completed1StandardScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model16.768329NaNsklearn...0.9767380.9733330.9754760.9954060.9733330.9972860.9733330.9733330.9962100.075481
1completed2MaxAbsScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model719.685673NaNsklearn...0.9767380.9733330.9754760.9952270.9733330.9972860.9733330.9733330.9962110.072493
2completed3RobustScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model1048.113134balancedsklearn...0.9683590.9666670.9664100.9942170.9666670.9965980.9666670.9666670.9955950.086160
\n", + "

3 rows × 31 columns

\n", + "
" + ], + "text/plain": [ + " state iter param.data_trans_class_name param.data_trans_module \\\n", + "0 completed 1 StandardScaler sklearn.preprocessing \n", + "1 completed 2 MaxAbsScaler sklearn.preprocessing \n", + "2 completed 3 RobustScaler sklearn.preprocessing \n", + "\n", + " param.data_trans_spec_class param.train_class_name param.train_module \\\n", + "0 preproc LogisticRegression sklearn.linear_model \n", + "1 preproc LogisticRegression sklearn.linear_model \n", + "2 preproc LogisticRegression sklearn.linear_model \n", + "\n", + " param.train_param_kwargs_C param.train_param_kwargs_class_weight \\\n", + "0 16.768329 NaN \n", + "1 719.685673 NaN \n", + "2 1048.113134 balanced \n", + "\n", + " param.train_spec_class ... output.precision_score_weighted \\\n", + "0 sklearn ... 0.976738 \n", + "1 sklearn ... 0.976738 \n", + "2 sklearn ... 0.968359 \n", + "\n", + " output.recall_score_micro output.precision_score_macro \\\n", + "0 0.973333 0.975476 \n", + "1 0.973333 0.975476 \n", + "2 0.966667 0.966410 \n", + "\n", + " output.average_precision_score_macro output.accuracy output.auc_weighted \\\n", + "0 0.995406 0.973333 0.997286 \n", + "1 0.995227 0.973333 0.997286 \n", + "2 0.994217 0.966667 0.996598 \n", + "\n", + " output.recall_score_weighted output.f1_score_micro \\\n", + "0 0.973333 0.973333 \n", + "1 0.973333 0.973333 \n", + "2 0.966667 0.966667 \n", + "\n", + " output.average_precision_score_micro output.log_loss \n", + "0 0.996210 0.075481 \n", + "1 0.996211 0.072493 \n", + "2 0.995595 0.086160 \n", + "\n", + "[3 rows x 31 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "azureml_run.artifact('iteration_results').show()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 4. Deploy Model Serving" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "mlrun-flow\n", + "\n", + "\n", + "\n", + "_start\n", + "\n", + "start\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "_start->\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LogisticRegression0\n", + "\n", + "LogisticRegression0\n", + "\n", + "\n", + "\n", + "->LogisticRegression0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LogisticRegression1\n", + "\n", + "LogisticRegression1\n", + "\n", + "\n", + "\n", + "->LogisticRegression1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LogisticRegression2\n", + "\n", + "LogisticRegression2\n", + "\n", + "\n", + "\n", + "->LogisticRegression2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LogisticRegression3\n", + "\n", + "LogisticRegression3\n", + "\n", + "\n", + "\n", + "->LogisticRegression3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LogisticRegression4\n", + "\n", + "LogisticRegression4\n", + "\n", + "\n", + "\n", + "->LogisticRegression4\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Importing serving function from marketplace:\n", + "serving_fn = mlrun.new_function(\"serving\", kind=\"serving\", image=\"yhaviv/mlrun:dev\")\n", + "serving_fn.with_code(body=\" \")\n", + "serving_fn.with_requirements(\"./requirements.txt\")\n", + "\n", + "# Set the real-time pipeline topology\n", + "serving_fn.set_topology(\n", + " 'router',\n", + " 'mlrun.serving.routers.VotingEnsemble'\n", + ")\n", + "\n", + "# Add the trained models:\n", + "artifacts = mlrun.get_run_db().list_artifacts(project=project.name)\n", + "models = {f\"{model['algorithm']}{i}\" :f\"{model['db_key']}#{model['iter']}\"\n", + " for i, model in enumerate(artifacts) if model[\"kind\"]==\"model\"}\n", + "\n", + "for name, path in models.items():\n", + " serving_fn.add_model(\n", + " name,\n", + " class_name=\"mlrun.frameworks.sklearn.PickleModelServer\",\n", + " model_path=project.get_artifact_uri(path))\n", + "\n", + "serving_fn.spec.graph.plot()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Building and Deploying the Serving Function" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-02-02 18:38:48,785 [info] Starting remote function deploy\n" + ] + } + ], + "source": [ + "function_address = serving_fn.deploy()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 5. Using the Live Model-Serving Function" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "print (f'The address for the function is {function_address} \\n')\n", + "\n", + "!curl $function_address" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Data for testing:\n", + "source_df = mlrun.get_dataitem(DATA_URL).as_df()\n", + "test_vector = source_df.sample(5).drop('label', axis=1).values.tolist()\n", + "test_vector" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "After deploying the serving function with the required model we can make prediction:" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "serving_fn.invoke(f'/v2/models/infer', {\"inputs\": test_vector})" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 6. Clean up\n", + "\n", + "For cleaning up AzureML resources see:\n", + "https://docs.microsoft.com/en-us/azure/machine-learning/tutorial-auto-train-models#clean-up-resources" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/src/azureml_utils.py b/functions/master/azureml_utils/1.4.0/src/azureml_utils.py new file mode 100644 index 00000000..041af2b8 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/azureml_utils.py @@ -0,0 +1,568 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os +import json +import logging +from typing import Tuple, List + +from mlrun import MLClientCtx, DataItem, get_dataitem +import mlrun.feature_store as f_store +import mlrun.datastore +import mlrun.utils +from mlrun.datastore.targets import ParquetTarget + +from azureml.core.authentication import ServicePrincipalAuthentication +from azureml.core.workspace import Workspace +from azureml.core.experiment import Experiment +from azureml.core.dataset import Dataset +from azureml.core.model import Model +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.compute_target import ComputeTargetException +from azureml.core.script_run import ScriptRun + +from azureml.train.automl import AutoMLConfig +from azureml.train.automl.run import AutoMLRun + + +def _env_or_secret(context, key): + if key in os.environ: + return os.environ[key] + return context.get_secret(key) + + +def _load_workspace(context: MLClientCtx) -> Workspace: + """ + Loading AzureML Workspace with Azure secrets. + + :param context: MLRun context. + :returns: AzureML Workspace + """ + + if hasattr(context, "_azure_workspace"): + return context._azure_workspace + + context.logger.info("Loading AzureML Workspace") + # Azure service authentication: + service_authentication = ServicePrincipalAuthentication( + tenant_id=_env_or_secret(context, "AZURE_TENANT_ID"), + service_principal_id=_env_or_secret(context, "AZURE_SERVICE_PRINCIPAL_ID"), + service_principal_password=_env_or_secret( + context, "AZURE_SERVICE_PRINCIPAL_PASSWORD" + ), + ) + + # Loading Azure workspace: + workspace = Workspace( + subscription_id=_env_or_secret(context, "AZURE_SUBSCRIPTION_ID"), + resource_group=_env_or_secret(context, "AZURE_RESOURCE_GROUP"), + workspace_name=_env_or_secret(context, "AZURE_WORKSPACE_NAME"), + auth=service_authentication, + ) + + context._azure_workspace = workspace + return workspace + + +def _init_experiment( + context: MLClientCtx, experiment_name: str +) -> Tuple[Workspace, Experiment]: + """ + Initialize workspace and experiment in Azure ML. Uses Service + Principal authentication via environment variables. + + :param context: MLRun context. + :param experiment_name: Name of experiment to create in Azure ML. + :returns: Azure ML Workspace and Experiment. + """ + + # Initialize experiment via Service Principal Authentication: + # https://docs.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication#use-service-principal-authentication + + workspace = _load_workspace(context) + + context.logger.info(f"Initializing AzureML experiment {experiment_name}") + # Creating experiment: + experiment = Experiment(workspace, experiment_name) + + return workspace, experiment + + +def init_compute( + context: MLClientCtx, + cpu_cluster_name: str, + vm_size: str = "STANDARD_D2_V2", + max_nodes: int = 1, +) -> ComputeTarget: + """ + Initialize Azure ML compute target to run experiment. Checks for + existing compute target and creates new if does not exist. + + :param context: MLRun context. + :param cpu_cluster_name: Name of Azure ML compute target. Created if does not exist. + :param vm_size: Azure machine type for compute target. + :param max_nodes: Maximum number of concurrent compute targets. + :returns: Azure ML Compute Target. + """ + + workspace = _load_workspace(context) + context.logger.info(f"Initializing AzureML compute target {cpu_cluster_name}") + + # Verify that cluster does not exist already: + try: + compute_target = ComputeTarget(workspace=workspace, name=cpu_cluster_name) + context.logger.info("Found existing cluster, will use it.") + except ComputeTargetException: + compute_config = AmlCompute.provisioning_configuration( + vm_size=vm_size, max_nodes=max_nodes + ) + compute_target = ComputeTarget.create( + workspace, cpu_cluster_name, compute_config + ) + + compute_target.wait_for_completion(show_output=True) + return compute_target + + +def register_dataset( + context: MLClientCtx, + dataset_name: str, + dataset_description: str, + data: DataItem, + create_new_version: bool = False, +): + """ + Register dataset object (can be also an Iguazio FeatureVector) in Azure ML. + Uploads parquet file to Azure blob storage and registers + that file as a dataset in Azure ML. + + :param context: MLRun context. + :param dataset_name: Name of Azure dataset to register. + :param dataset_description: Description of Azure dataset to register. + :param data: MLRun FeatureVector or dataset object to upload. + :param create_new_version: Register Azure dataset as new version. Must be used when + modifying dataset schema. + """ + + # test for Azure storage connection environment variable or secret: + assert _env_or_secret( + context, "AZURE_STORAGE_CONNECTION_STRING" + ), "AZURE_STORAGE_CONNECTION_STRING secret not set" + + # Connect to AzureML experiment and datastore: + context.logger.info("Connecting to AzureML experiment default datastore") + + workspace = _load_workspace(context) + datastore = workspace.get_default_datastore() + + # Azure blob path (default datastore for workspace): + blob_path = f"az://{datastore.container_name}/{dataset_name}" + + store_uri_prefix, _ = mlrun.datastore.parse_store_uri(data.artifact_url) + feature_vector_case = mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix + # Retrieve data source as dataframe: + if feature_vector_case: + # FeatureVector case: + context.logger.info( + f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}" + ) + f_store.get_offline_features(data.meta.uri, target=ParquetTarget(path=blob_path)) + else: + blob_path += data.suffix + # DataItem case: + context.logger.info( + f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}" + ) + data_in_bytes = data.get() + get_dataitem(blob_path).put(data_in_bytes) + + # Register dataset in AzureML: + context.logger.info(f"Registering dataset {dataset_name} in Azure ML") + if data.suffix == ".parquet" or feature_vector_case: + dataset = Dataset.Tabular.from_parquet_files( + path=(datastore, f"{dataset_name}.parquet"), validate=False + ) + else: + context.logger.info( + f"OpenSSL version must be 1.1. Overriding the OpenSSL version to 1.1" + ) + # OpenSSL version must be 1.1 + os.environ["CLR_OPENSSL_VERSION_OVERRIDE"] = "1.1" + dataset = Dataset.Tabular.from_delimited_files( + path=(datastore, f"{dataset_name}{data.suffix}"), validate=False + ) + + dataset.register( + workspace=workspace, + name=dataset_name, + description=dataset_description, + create_new_version=create_new_version, + ) + + # Output registered dataset name in Azure: + context.log_result("dataset_blob_path", blob_path) + + +def download_model( + context: MLClientCtx, + model_name: str, + model_version: int, + target_dir: str = ".", +) -> None: + """ + Download trained model from Azure ML to local filesystem. + + :param context: MLRun context. + :param model_name: Name of trained and registered model. + :param model_version: Version of model to download. + :param target_dir: Target directory to download model. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + context.logger.info(f"Downloading model {model_name}:{model_version}") + model = Model(workspace, model_name, version=model_version) + model.download(target_dir=target_dir, exist_ok=True) + + +def upload_model( + context: MLClientCtx, + model_name: str, + model_path: str, + model_description: str = None, + model_tags: dict = None, +) -> None: + """ + Upload pre-trained model from local filesystem to Azure ML. + :param context: MLRun context. + :param model_name: Name of trained and registered model. + :param model_path: Path to file on local filesystem. + :param model_description: Description of models. + :param model_tags: KV pairs of model tags. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + + context.logger.info(f"Upload model {model_name} from {model_path}") + Model.register( + workspace=workspace, + model_path=model_path, + model_name=model_name, + description=model_description, + tags=model_tags, + ) + + +def _get_top_n_runs( + remote_run: AutoMLRun, n: int = 5, primary_metric: str = "accuracy" +) -> List[ScriptRun]: + """ + Get top N complete runs from experiment sorted by primary metric. + + :param remote_run: Azure ML Run. + :param n: Number of top runs to return. + :param primary_metric: Metric to sort by. + + :returns: List of top N runs sorted by primary metric. + """ + # Collect all models: + complete_runs = [ + run + for run in remote_run.get_children(status="Completed") + if not any(s in run.id for s in ["setup", "worker"]) + ] + + # Checking that the required number of runs are done: + if len(complete_runs) < n: + raise ValueError(f"Expected {n} runs but only received {len(complete_runs)}") + + # Sorting by the primary metric: + sorted_runs = sorted( + complete_runs, key=lambda run: run.get_metrics()[primary_metric], reverse=True + ) + return sorted_runs[:n] + + +def _get_model_hp( + run: ScriptRun, +) -> dict: + """ + Get hyper-parameters of trained AzureML model. + Combine the hyper-parameters of the data transformation and training to a dictionary. + The prefix of the dictionary keys corresponds to 'data transformation' and 'training'. + + :param run: Run object of AzureML trained model. + + :returns: A dictionary as described in the docstring. + """ + + spec_field = "pipeline_spec" + if spec_field not in run.properties: + return {} + spec_string = run.properties[spec_field] + spec_dict = json.loads(spec_string) + + if "objects" not in spec_dict: + # No hyper-params + return {} + hp_dicts = spec_dict["objects"] + # after training there are two hyper-parameters dicts inside the run object: + assert ( + len(hp_dicts) == 2 + ), "after training there are two hyper-parameters dicts inside the run object" + result_dict = {} + dict_keys = [ + ["data_trans_class_name", "data_trans_module", "data_trans_spec_class"], + [ + "train_class_name", + "train_module", + "train_param_kwargs_C", + "train_param_kwargs_class_weight", + "train_spec_class", + ], + ] + + # creating hyper-params dict with key prefixes for each part: + kwargs_prefix = "param_kwargs" + for d, name, keys in zip(hp_dicts, ["data_trans", "train"], dict_keys): + for key in keys: + + if kwargs_prefix in key: + result_dict[key] = d[kwargs_prefix][ + key.replace(f"{name}_{kwargs_prefix}_", "") + ] + else: + result_dict[key] = d[key.replace(f"{name}_", "")] + if not result_dict[key]: + result_dict[key] = "" + + return result_dict + + +def submit_training_job( + context: MLClientCtx, + experiment: Experiment, + compute_target: ComputeTarget, + register_model_name: str, + registered_dataset_name: str, + automl_settings: dict, + training_set: DataItem, + label_column_name: str = '', + save_n_models: int = 3, + show_output: bool = True, +) -> None: + """ + Submit training job to Azure AutoML and download trained model + when completed. Uses previously registered dataset for training. + + :param context: MLRun context. + :param experiment: Azure experiment. + :param compute_target: Azure compute target. + :param register_model_name: Name of model to register in Azure. + :param registered_dataset_name: Name of dataset registered in Azure ML. + :param label_column_name: Name of target column in dataset. + :param automl_settings: JSON string of all Azure AutoML settings. + :param training_set: Training set to log with model. For model + monitoring integration. + :param show_output: Displaying Azure logs. + :param save_n_models: How many of the top performing models to log. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + + # Setup experiment: + context.logger.info("Setting up experiment parameters") + dataset = Dataset.get_by_name(workspace, name=registered_dataset_name) + + # Get training set to log with model: + feature_vector = None + store_uri_prefix, _ = mlrun.datastore.parse_store_uri(training_set.artifact_url) + if mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix: + feature_vector = training_set.meta.uri + label_column_name = label_column_name or training_set.meta.status.label_column + context.logger.info(f'label column name: {label_column_name}') + training_set = f_store.get_offline_features(feature_vector).to_dataframe() + else: + training_set = training_set.as_df() + + automl_config = AutoMLConfig( + compute_target=compute_target, + training_data=dataset, + verbosity=logging.INFO, + label_column_name=label_column_name, + **automl_settings, + ) + + # Run experiment on AzureML: + context.logger.info("Submitting and running experiment") + remote_run = experiment.submit(automl_config) + remote_run.wait_for_completion(show_output=show_output) + if show_output: + # Azure log ending row: + print(f"\n{'*' * 92}\n") + # Get top N runs to log: + top_runs = _get_top_n_runs( + remote_run=remote_run, + n=save_n_models, + primary_metric=automl_settings["primary_metric"], + ) + + # Register, download, and log models: + for i, run in enumerate(top_runs): + # Register model: + context.logger.info("Registering model") + model = run.register_model( + model_name=register_model_name, model_path="outputs/model.pkl" + ) + context.logger.info( + f"Registered model with name '{model.name}', id '{model.id}', version '{model.version}'" + ) + + # Download model locally: + download_model( + context=context, + model_name=register_model_name, + model_version=model.version, + target_dir=f"./{model.version}", + ) + + metrics = {k.lower(): val for k, val in run.get_metrics().items()} + del metrics["confusion_matrix"] + del metrics["accuracy_table"] + + # Collect model hyper-parameters: + model_hp_dict = _get_model_hp(run) + with context.get_child_context(**model_hp_dict) as child: + model_key = f"model_{i + 1}_{model_hp_dict['data_trans_class_name'].lower()}_{model_hp_dict['train_class_name'].lower()}" + # Log model: + context.logger.info( + f"Logging {model_key} model to MLRun" + ) + child.log_results(metrics) + child.log_model( + "model", + db_key=model_key, + artifact_path=context.artifact_subpath("models"), + metrics=metrics, + model_file=f"{model.version}/model.pkl", + training_set=training_set, + label_column=label_column_name, + feature_vector=feature_vector, + framework="AzureML", + algorithm=model_hp_dict.get("train_class_name"), + ) + if i == 0: + # This also logs the model: + child.mark_as_best() + + +def train( + # MlRun + context: MLClientCtx, + dataset: DataItem, + # Init experiment and compute + experiment_name: str = "", + cpu_cluster_name: str = "", + vm_size: str = "STANDARD_D2_V2", + max_nodes: int = 1, + # Register dataset + dataset_name: str = "", + dataset_description: str = "", + create_new_version: bool = False, + label_column_name: str = "", + # Submit training job + register_model_name: str = "", + save_n_models: int = 1, + log_azure: bool = True, + automl_settings: str = None, +) -> None: + """ + Whole training flow for Azure AutoML. Registers dataset/feature vector, + submits training job to Azure AutoML, and downloads trained model + when completed. + + :param context: MLRun context. + + :param dataset: MLRun FeatureVector or dataset URI to upload. Will drop + index before uploading when it is a FeatureVector. + + :param experiment_name: Name of experiment to create in Azure ML. + :param cpu_cluster_name: Name of Azure ML compute target. Created if does not exist. + :param vm_size: Azure machine type for compute target. + :param max_nodes: Maximum number of concurrent compute targets. + + :param dataset_name: Name of Azure dataset to register. + :param dataset_description: Description of Azure dataset to register. + + :param create_new_version: Register Azure dataset as new version. Must be used when + modifying dataset schema. + :param label_column_name: Target column in dataset. + + :param register_model_name: Name of model to register in Azure. + :param save_n_models: How many of the top performing models to log. + :param log_azure: Displaying Azure logs. + :param automl_settings: JSON string of all Azure AutoML settings. + """ + if not automl_settings: + automl_settings = { + "task": "classification", + "debug_log": "automl_errors.log", + # "experiment_exit_score": 0.9, + "enable_early_stopping": False, + "allowed_models": ["LogisticRegression", "SGD", "SVM"], + "iterations": 3, + "iteration_timeout_minutes": 2, + "max_concurrent_iterations": 2, + "max_cores_per_iteration": -1, + "n_cross_validations": 5, + "primary_metric": "accuracy", + "featurization": "off", + "model_explainability": False, + "enable_voting_ensemble": False, + "enable_stack_ensemble": False, + } + + # Init experiment and compute + workspace, experiment = _init_experiment( + context=context, experiment_name=experiment_name + ) + + compute_target = init_compute( + context=context, + cpu_cluster_name=cpu_cluster_name, + vm_size=vm_size, + max_nodes=max_nodes, + ) + + # Register dataset + register_dataset( + context=context, + dataset_name=dataset_name, + dataset_description=dataset_description, + data=dataset, + create_new_version=create_new_version, + ) + + # Submit training job + submit_training_job( + context, + experiment=experiment, + compute_target=compute_target, + register_model_name=register_model_name, + registered_dataset_name=dataset_name, + label_column_name=label_column_name, + automl_settings=automl_settings, + training_set=dataset, + show_output=log_azure, + save_n_models=save_n_models, + ) diff --git a/functions/master/azureml_utils/1.4.0/src/function.yaml b/functions/master/azureml_utils/1.4.0/src/function.yaml new file mode 100644 index 00000000..a6348996 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/function.yaml @@ -0,0 +1,247 @@ +verbose: false +spec: + command: '' + build: + auto_build: true + code_origin: '' + with_mlrun: true + requirements: + - azureml-core==1.54.0.post1 + - azureml-train-automl-client==1.54.0.post1 + - plotly~=5.4 + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK + commands: + - apt-get update && apt-get install -y --no-install-recommends git + - apt install -y liblttng-ust0 + base_image: python:3.9-bullseye + origin_filename: '' + default_handler: train + allow_empty_resources: true + disable_auto_mount: false + image: '' + entry_points: + init_compute: + doc: 'Initialize Azure ML compute target to run experiment. Checks for + + existing compute target and creates new if does not exist.' + name: init_compute + lineno: 102 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: cpu_cluster_name + type: str + doc: Name of Azure ML compute target. Created if does not exist. + - name: vm_size + type: str + doc: Azure machine type for compute target. + default: STANDARD_D2_V2 + - name: max_nodes + type: int + doc: Maximum number of concurrent compute targets. + default: 1 + outputs: + - doc: Azure ML Compute Target. + type: ComputeTarget + has_varargs: false + register_dataset: + doc: 'Register dataset object (can be also an Iguazio FeatureVector) in Azure + ML. + + Uploads parquet file to Azure blob storage and registers + + that file as a dataset in Azure ML.' + name: register_dataset + lineno: 138 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: dataset_name + type: str + doc: Name of Azure dataset to register. + - name: dataset_description + type: str + doc: Description of Azure dataset to register. + - name: data + type: DataItem + doc: MLRun FeatureVector or dataset object to upload. + - name: create_new_version + type: bool + doc: Register Azure dataset as new version. Must be used when modifying dataset + schema. + default: false + has_varargs: false + download_model: + doc: Download trained model from Azure ML to local filesystem. + name: download_model + lineno: 217 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: model_name + type: str + doc: Name of trained and registered model. + - name: model_version + type: int + doc: Version of model to download. + - name: target_dir + type: str + doc: Target directory to download model. + default: . + outputs: + - type: None + has_varargs: false + upload_model: + doc: Upload pre-trained model from local filesystem to Azure ML. + name: upload_model + lineno: 238 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: model_name + type: str + doc: Name of trained and registered model. + - name: model_path + type: str + doc: Path to file on local filesystem. + - name: model_description + type: str + doc: Description of models. + default: null + - name: model_tags + type: dict + doc: KV pairs of model tags. + default: null + outputs: + - type: None + has_varargs: false + submit_training_job: + doc: 'Submit training job to Azure AutoML and download trained model + + when completed. Uses previously registered dataset for training.' + name: submit_training_job + lineno: 352 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: experiment + type: Experiment + doc: Azure experiment. + - name: compute_target + type: ComputeTarget + doc: Azure compute target. + - name: register_model_name + type: str + doc: Name of model to register in Azure. + - name: registered_dataset_name + type: str + doc: Name of dataset registered in Azure ML. + - name: automl_settings + type: dict + doc: JSON string of all Azure AutoML settings. + - name: training_set + type: DataItem + doc: Training set to log with model. For model monitoring integration. + - name: label_column_name + type: str + doc: Name of target column in dataset. + default: '' + - name: save_n_models + type: int + doc: How many of the top performing models to log. + default: 3 + - name: show_output + type: bool + doc: Displaying Azure logs. + default: true + outputs: + - type: None + has_varargs: false + train: + doc: 'Whole training flow for Azure AutoML. Registers dataset/feature vector, + + submits training job to Azure AutoML, and downloads trained model + + when completed.' + name: train + lineno: 469 + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: dataset + type: DataItem + doc: MLRun FeatureVector or dataset URI to upload. Will drop index before + uploading when it is a FeatureVector. + - name: experiment_name + type: str + doc: Name of experiment to create in Azure ML. + default: '' + - name: cpu_cluster_name + type: str + doc: Name of Azure ML compute target. Created if does not exist. + default: '' + - name: vm_size + type: str + doc: Azure machine type for compute target. + default: STANDARD_D2_V2 + - name: max_nodes + type: int + doc: Maximum number of concurrent compute targets. + default: 1 + - name: dataset_name + type: str + doc: Name of Azure dataset to register. + default: '' + - name: dataset_description + type: str + doc: Description of Azure dataset to register. + default: '' + - name: create_new_version + type: bool + doc: Register Azure dataset as new version. Must be used when modifying dataset + schema. + default: false + - name: label_column_name + type: str + doc: Target column in dataset. + default: '' + - name: register_model_name + type: str + doc: Name of model to register in Azure. + default: '' + - name: save_n_models + type: int + doc: How many of the top performing models to log. + default: 1 + - name: log_azure + type: bool + doc: Displaying Azure logs. + default: true + - name: automl_settings + type: str + doc: JSON string of all Azure AutoML settings. + default: null + outputs: + - type: None + has_varargs: false + description: Azure AutoML integration in MLRun, including utils functions for training + models on Azure AutoML platfrom. +kind: job +metadata: + categories: + - model-serving + - utils + tag: '' + name: azureml-utils diff --git a/functions/master/azureml_utils/1.4.0/src/item.yaml b/functions/master/azureml_utils/1.4.0/src/item.yaml new file mode 100644 index 00000000..0b4d5e49 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/item.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +categories: +- model-serving +- utils +description: Azure AutoML integration in MLRun, including utils functions for training + models on Azure AutoML platfrom. +doc: '' +example: azureml_utils.ipynb +generationDate: 2022-08-28:17-25 +hidden: false +icon: '' +labels: + author: yonish +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: azureml_utils +platformVersion: 3.5.3 +spec: + extra_spec: + allow_empty_resources: true + build: + auto_build: true + commands: + - apt-get update && apt-get install -y --no-install-recommends git + - apt install -y liblttng-ust0 + with_mlrun: true + filename: azureml_utils.py + handler: train + image: python:3.9-bullseye + kind: job + requirements: + - azureml-core==1.54.0.post1 + - azureml-train-automl-client==1.54.0.post1 + - plotly~=5.4 +url: '' +version: 1.4.0 +test_valid: True diff --git a/functions/master/azureml_utils/1.4.0/src/requirements.txt b/functions/master/azureml_utils/1.4.0/src/requirements.txt new file mode 100644 index 00000000..b5486614 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/requirements.txt @@ -0,0 +1,3 @@ +azureml-core==1.54.0.post1 +azureml-train-automl-client==1.54.0.post1 +plotly~=5.4 \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/src/test_azureml_utils.py b/functions/master/azureml_utils/1.4.0/src/test_azureml_utils.py new file mode 100644 index 00000000..d6ef80d1 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/src/test_azureml_utils.py @@ -0,0 +1,128 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os +import tempfile +import shutil +import pytest + +import mlrun +from mlrun import import_function + +EXPERIMENT_NAME = "azure-automl-test" +PROJECT_NAME = "azure-automl-project" + +DATA_URL = "https://s3.wasabisys.com/iguazio/data/iris/iris_dataset.csv" + +SECRETS_REQUIRED_FIELDS = [ + "AZURE_TENANT_ID", + "AZURE_SERVICE_PRINCIPAL_ID", + "AZURE_SERVICE_PRINCIPAL_PASSWORD", + "AZURE_SUBSCRIPTION_ID", + "AZURE_RESOURCE_GROUP", + "AZURE_WORKSPACE_NAME", + "AZURE_STORAGE_CONNECTION_STRING", +] + + +def _validate_environment_variables() -> bool: + environment_keys = os.environ.keys() + return all(key in environment_keys for key in SECRETS_REQUIRED_FIELDS) + + +def _get_secrets_spec(): + return mlrun.new_task().with_secrets( + "env", + ",".join(SECRETS_REQUIRED_FIELDS), + ) + + +def _set_environment(): + artifact_path = tempfile.TemporaryDirectory().name + os.makedirs(artifact_path) + return artifact_path + + +def _cleanup_environment(artifact_path: str): + """ + Cleanup the test environment, deleting files and artifacts created during the test. + + :param artifact_path: The artifact path to delete. + """ + # Clean the local directory: + for test_output in [ + *os.listdir(artifact_path), + "schedules", + "runs", + "artifacts", + "functions", + ]: + test_output_path = os.path.abspath(f"./{test_output}") + if os.path.exists(test_output_path): + if os.path.isdir(test_output_path): + shutil.rmtree(test_output_path) + else: + os.remove(test_output_path) + + # Clean the artifacts' directory: + shutil.rmtree(artifact_path) + + +@pytest.mark.skipif( + condition=not _validate_environment_variables(), + reason="AzureML secrets should be provided as environment variables", +) +def test_train(): + """ + Test the 'train' handler with iris dataset. + """ + test_pass = False + + # Setting secrets: + secrets_spec = _get_secrets_spec() + + # Setting environment: + artifact_path = _set_environment() + azure_automl_fn = import_function("function.yaml") + model_paths, save_n_models = [], 2 + + try: + azureml_run = azure_automl_fn.run( + runspec=secrets_spec, + handler="train", + params={ + "experiment_name": EXPERIMENT_NAME, + "cpu_cluster_name": "azureml-cpu", + "dataset_name": "iris-test", + "log_azure": False, + "dataset_description": "iris training data", + "label_column_name": "label", + "create_new_version": True, + "register_model_name": "iris-model", + "save_n_models": save_n_models, + }, + inputs={"dataset": DATA_URL}, + artifact_path=artifact_path, + local=True, + ) + # Get trained models: + num_saved_models = len(azureml_run.status.iterations) - 1 # The first one in the list is the 'columns' + test_pass = num_saved_models == save_n_models + + except Exception as exception: + print(f"- The test failed - raised the following error:\n- {exception}") + + _cleanup_environment(artifact_path) + + assert test_pass, f'Created {len(model_paths)} models instead of {save_n_models}' diff --git a/functions/master/azureml_utils/1.4.0/static/azureml_utils.html b/functions/master/azureml_utils/1.4.0/static/azureml_utils.html new file mode 100644 index 00000000..9a6dc0d3 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/azureml_utils.html @@ -0,0 +1,758 @@ + + + + + + + +azureml_utils.azureml_utils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ +
+
+
+
+
+ +
+

Source code for azureml_utils.azureml_utils

+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+import json
+import logging
+from typing import Tuple, List
+
+from mlrun import MLClientCtx, DataItem, get_dataitem
+import mlrun.feature_store as f_store
+import mlrun.datastore
+import mlrun.utils
+from mlrun.datastore.targets import ParquetTarget
+
+from azureml.core.authentication import ServicePrincipalAuthentication
+from azureml.core.workspace import Workspace
+from azureml.core.experiment import Experiment
+from azureml.core.dataset import Dataset
+from azureml.core.model import Model
+from azureml.core.compute import ComputeTarget, AmlCompute
+from azureml.core.compute_target import ComputeTargetException
+from azureml.core.script_run import ScriptRun
+
+from azureml.train.automl import AutoMLConfig
+from azureml.train.automl.run import AutoMLRun
+
+
+def _env_or_secret(context, key):
+    if key in os.environ:
+        return os.environ[key]
+    return context.get_secret(key)
+
+
+def _load_workspace(context: MLClientCtx) -> Workspace:
+    """
+    Loading AzureML Workspace with Azure secrets.
+
+    :param context: MLRun context.
+    :returns:       AzureML Workspace
+    """
+
+    if hasattr(context, "_azure_workspace"):
+        return context._azure_workspace
+
+    context.logger.info("Loading AzureML Workspace")
+    # Azure service authentication:
+    service_authentication = ServicePrincipalAuthentication(
+        tenant_id=_env_or_secret(context, "AZURE_TENANT_ID"),
+        service_principal_id=_env_or_secret(context, "AZURE_SERVICE_PRINCIPAL_ID"),
+        service_principal_password=_env_or_secret(
+            context, "AZURE_SERVICE_PRINCIPAL_PASSWORD"
+        ),
+    )
+
+    # Loading Azure workspace:
+    workspace = Workspace(
+        subscription_id=_env_or_secret(context, "AZURE_SUBSCRIPTION_ID"),
+        resource_group=_env_or_secret(context, "AZURE_RESOURCE_GROUP"),
+        workspace_name=_env_or_secret(context, "AZURE_WORKSPACE_NAME"),
+        auth=service_authentication,
+    )
+
+    context._azure_workspace = workspace
+    return workspace
+
+
+def _init_experiment(
+    context: MLClientCtx, experiment_name: str
+) -> Tuple[Workspace, Experiment]:
+    """
+    Initialize workspace and experiment in Azure ML. Uses Service
+    Principal authentication via environment variables.
+
+    :param context:         MLRun context.
+    :param experiment_name: Name of experiment to create in Azure ML.
+    :returns:               Azure ML Workspace and Experiment.
+    """
+
+    # Initialize experiment via Service Principal Authentication:
+    # https://docs.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication#use-service-principal-authentication
+
+    workspace = _load_workspace(context)
+
+    context.logger.info(f"Initializing AzureML experiment {experiment_name}")
+    # Creating experiment:
+    experiment = Experiment(workspace, experiment_name)
+
+    return workspace, experiment
+
+
+
+[docs] +def init_compute( + context: MLClientCtx, + cpu_cluster_name: str, + vm_size: str = "STANDARD_D2_V2", + max_nodes: int = 1, +) -> ComputeTarget: + """ + Initialize Azure ML compute target to run experiment. Checks for + existing compute target and creates new if does not exist. + + :param context: MLRun context. + :param cpu_cluster_name: Name of Azure ML compute target. Created if does not exist. + :param vm_size: Azure machine type for compute target. + :param max_nodes: Maximum number of concurrent compute targets. + :returns: Azure ML Compute Target. + """ + + workspace = _load_workspace(context) + context.logger.info(f"Initializing AzureML compute target {cpu_cluster_name}") + + # Verify that cluster does not exist already: + try: + compute_target = ComputeTarget(workspace=workspace, name=cpu_cluster_name) + context.logger.info("Found existing cluster, will use it.") + except ComputeTargetException: + compute_config = AmlCompute.provisioning_configuration( + vm_size=vm_size, max_nodes=max_nodes + ) + compute_target = ComputeTarget.create( + workspace, cpu_cluster_name, compute_config + ) + + compute_target.wait_for_completion(show_output=True) + return compute_target
+ + + +
+[docs] +def register_dataset( + context: MLClientCtx, + dataset_name: str, + dataset_description: str, + data: DataItem, + create_new_version: bool = False, +): + """ + Register dataset object (can be also an Iguazio FeatureVector) in Azure ML. + Uploads parquet file to Azure blob storage and registers + that file as a dataset in Azure ML. + + :param context: MLRun context. + :param dataset_name: Name of Azure dataset to register. + :param dataset_description: Description of Azure dataset to register. + :param data: MLRun FeatureVector or dataset object to upload. + :param create_new_version: Register Azure dataset as new version. Must be used when + modifying dataset schema. + """ + + # test for Azure storage connection environment variable or secret: + assert _env_or_secret( + context, "AZURE_STORAGE_CONNECTION_STRING" + ), "AZURE_STORAGE_CONNECTION_STRING secret not set" + + # Connect to AzureML experiment and datastore: + context.logger.info("Connecting to AzureML experiment default datastore") + + workspace = _load_workspace(context) + datastore = workspace.get_default_datastore() + + # Azure blob path (default datastore for workspace): + blob_path = f"az://{datastore.container_name}/{dataset_name}" + + store_uri_prefix, _ = mlrun.datastore.parse_store_uri(data.artifact_url) + feature_vector_case = mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix + # Retrieve data source as dataframe: + if feature_vector_case: + # FeatureVector case: + context.logger.info( + f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}" + ) + f_store.get_offline_features(data.meta.uri, target=ParquetTarget(path=blob_path)) + else: + blob_path += data.suffix + # DataItem case: + context.logger.info( + f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}" + ) + data_in_bytes = data.get() + get_dataitem(blob_path).put(data_in_bytes) + + # Register dataset in AzureML: + context.logger.info(f"Registering dataset {dataset_name} in Azure ML") + if data.suffix == ".parquet" or feature_vector_case: + dataset = Dataset.Tabular.from_parquet_files( + path=(datastore, f"{dataset_name}.parquet"), validate=False + ) + else: + context.logger.info( + f"OpenSSL version must be 1.1. Overriding the OpenSSL version to 1.1" + ) + # OpenSSL version must be 1.1 + os.environ["CLR_OPENSSL_VERSION_OVERRIDE"] = "1.1" + dataset = Dataset.Tabular.from_delimited_files( + path=(datastore, f"{dataset_name}{data.suffix}"), validate=False + ) + + dataset.register( + workspace=workspace, + name=dataset_name, + description=dataset_description, + create_new_version=create_new_version, + ) + + # Output registered dataset name in Azure: + context.log_result("dataset_blob_path", blob_path)
+ + + +
+[docs] +def download_model( + context: MLClientCtx, + model_name: str, + model_version: int, + target_dir: str = ".", +) -> None: + """ + Download trained model from Azure ML to local filesystem. + + :param context: MLRun context. + :param model_name: Name of trained and registered model. + :param model_version: Version of model to download. + :param target_dir: Target directory to download model. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + context.logger.info(f"Downloading model {model_name}:{model_version}") + model = Model(workspace, model_name, version=model_version) + model.download(target_dir=target_dir, exist_ok=True)
+ + + +
+[docs] +def upload_model( + context: MLClientCtx, + model_name: str, + model_path: str, + model_description: str = None, + model_tags: dict = None, +) -> None: + """ + Upload pre-trained model from local filesystem to Azure ML. + :param context: MLRun context. + :param model_name: Name of trained and registered model. + :param model_path: Path to file on local filesystem. + :param model_description: Description of models. + :param model_tags: KV pairs of model tags. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + + context.logger.info(f"Upload model {model_name} from {model_path}") + Model.register( + workspace=workspace, + model_path=model_path, + model_name=model_name, + description=model_description, + tags=model_tags, + )
+ + + +def _get_top_n_runs( + remote_run: AutoMLRun, n: int = 5, primary_metric: str = "accuracy" +) -> List[ScriptRun]: + """ + Get top N complete runs from experiment sorted by primary metric. + + :param remote_run: Azure ML Run. + :param n: Number of top runs to return. + :param primary_metric: Metric to sort by. + + :returns: List of top N runs sorted by primary metric. + """ + # Collect all models: + complete_runs = [ + run + for run in remote_run.get_children(status="Completed") + if not any(s in run.id for s in ["setup", "worker"]) + ] + + # Checking that the required number of runs are done: + if len(complete_runs) < n: + raise ValueError(f"Expected {n} runs but only received {len(complete_runs)}") + + # Sorting by the primary metric: + sorted_runs = sorted( + complete_runs, key=lambda run: run.get_metrics()[primary_metric], reverse=True + ) + return sorted_runs[:n] + + +def _get_model_hp( + run: ScriptRun, +) -> dict: + """ + Get hyper-parameters of trained AzureML model. + Combine the hyper-parameters of the data transformation and training to a dictionary. + The prefix of the dictionary keys corresponds to 'data transformation' and 'training'. + + :param run: Run object of AzureML trained model. + + :returns: A dictionary as described in the docstring. + """ + + spec_field = "pipeline_spec" + if spec_field not in run.properties: + return {} + spec_string = run.properties[spec_field] + spec_dict = json.loads(spec_string) + + if "objects" not in spec_dict: + # No hyper-params + return {} + hp_dicts = spec_dict["objects"] + # after training there are two hyper-parameters dicts inside the run object: + assert ( + len(hp_dicts) == 2 + ), "after training there are two hyper-parameters dicts inside the run object" + result_dict = {} + dict_keys = [ + ["data_trans_class_name", "data_trans_module", "data_trans_spec_class"], + [ + "train_class_name", + "train_module", + "train_param_kwargs_C", + "train_param_kwargs_class_weight", + "train_spec_class", + ], + ] + + # creating hyper-params dict with key prefixes for each part: + kwargs_prefix = "param_kwargs" + for d, name, keys in zip(hp_dicts, ["data_trans", "train"], dict_keys): + for key in keys: + + if kwargs_prefix in key: + result_dict[key] = d[kwargs_prefix][ + key.replace(f"{name}_{kwargs_prefix}_", "") + ] + else: + result_dict[key] = d[key.replace(f"{name}_", "")] + if not result_dict[key]: + result_dict[key] = "" + + return result_dict + + +
+[docs] +def submit_training_job( + context: MLClientCtx, + experiment: Experiment, + compute_target: ComputeTarget, + register_model_name: str, + registered_dataset_name: str, + automl_settings: dict, + training_set: DataItem, + label_column_name: str = '', + save_n_models: int = 3, + show_output: bool = True, +) -> None: + """ + Submit training job to Azure AutoML and download trained model + when completed. Uses previously registered dataset for training. + + :param context: MLRun context. + :param experiment: Azure experiment. + :param compute_target: Azure compute target. + :param register_model_name: Name of model to register in Azure. + :param registered_dataset_name: Name of dataset registered in Azure ML. + :param label_column_name: Name of target column in dataset. + :param automl_settings: JSON string of all Azure AutoML settings. + :param training_set: Training set to log with model. For model + monitoring integration. + :param show_output: Displaying Azure logs. + :param save_n_models: How many of the top performing models to log. + """ + # Loading workspace if not provided: + workspace = _load_workspace(context) + + # Setup experiment: + context.logger.info("Setting up experiment parameters") + dataset = Dataset.get_by_name(workspace, name=registered_dataset_name) + + # Get training set to log with model: + feature_vector = None + store_uri_prefix, _ = mlrun.datastore.parse_store_uri(training_set.artifact_url) + if mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix: + feature_vector = training_set.meta.uri + label_column_name = label_column_name or training_set.meta.status.label_column + context.logger.info(f'label column name: {label_column_name}') + training_set = f_store.get_offline_features(feature_vector).to_dataframe() + else: + training_set = training_set.as_df() + + automl_config = AutoMLConfig( + compute_target=compute_target, + training_data=dataset, + verbosity=logging.INFO, + label_column_name=label_column_name, + **automl_settings, + ) + + # Run experiment on AzureML: + context.logger.info("Submitting and running experiment") + remote_run = experiment.submit(automl_config) + remote_run.wait_for_completion(show_output=show_output) + if show_output: + # Azure log ending row: + print(f"\n{'*' * 92}\n") + # Get top N runs to log: + top_runs = _get_top_n_runs( + remote_run=remote_run, + n=save_n_models, + primary_metric=automl_settings["primary_metric"], + ) + + # Register, download, and log models: + for i, run in enumerate(top_runs): + # Register model: + context.logger.info("Registering model") + model = run.register_model( + model_name=register_model_name, model_path="outputs/model.pkl" + ) + context.logger.info( + f"Registered model with name '{model.name}', id '{model.id}', version '{model.version}'" + ) + + # Download model locally: + download_model( + context=context, + model_name=register_model_name, + model_version=model.version, + target_dir=f"./{model.version}", + ) + + metrics = {k.lower(): val for k, val in run.get_metrics().items()} + del metrics["confusion_matrix"] + del metrics["accuracy_table"] + + # Collect model hyper-parameters: + model_hp_dict = _get_model_hp(run) + with context.get_child_context(**model_hp_dict) as child: + model_key = f"model_{i + 1}_{model_hp_dict['data_trans_class_name'].lower()}_{model_hp_dict['train_class_name'].lower()}" + # Log model: + context.logger.info( + f"Logging {model_key} model to MLRun" + ) + child.log_results(metrics) + child.log_model( + "model", + db_key=model_key, + artifact_path=context.artifact_subpath("models"), + metrics=metrics, + model_file=f"{model.version}/model.pkl", + training_set=training_set, + label_column=label_column_name, + feature_vector=feature_vector, + framework="AzureML", + algorithm=model_hp_dict.get("train_class_name"), + ) + if i == 0: + # This also logs the model: + child.mark_as_best()
+ + + +
+[docs] +def train( + # MlRun + context: MLClientCtx, + dataset: DataItem, + # Init experiment and compute + experiment_name: str = "", + cpu_cluster_name: str = "", + vm_size: str = "STANDARD_D2_V2", + max_nodes: int = 1, + # Register dataset + dataset_name: str = "", + dataset_description: str = "", + create_new_version: bool = False, + label_column_name: str = "", + # Submit training job + register_model_name: str = "", + save_n_models: int = 1, + log_azure: bool = True, + automl_settings: str = None, +) -> None: + """ + Whole training flow for Azure AutoML. Registers dataset/feature vector, + submits training job to Azure AutoML, and downloads trained model + when completed. + + :param context: MLRun context. + + :param dataset: MLRun FeatureVector or dataset URI to upload. Will drop + index before uploading when it is a FeatureVector. + + :param experiment_name: Name of experiment to create in Azure ML. + :param cpu_cluster_name: Name of Azure ML compute target. Created if does not exist. + :param vm_size: Azure machine type for compute target. + :param max_nodes: Maximum number of concurrent compute targets. + + :param dataset_name: Name of Azure dataset to register. + :param dataset_description: Description of Azure dataset to register. + + :param create_new_version: Register Azure dataset as new version. Must be used when + modifying dataset schema. + :param label_column_name: Target column in dataset. + + :param register_model_name: Name of model to register in Azure. + :param save_n_models: How many of the top performing models to log. + :param log_azure: Displaying Azure logs. + :param automl_settings: JSON string of all Azure AutoML settings. + """ + if not automl_settings: + automl_settings = { + "task": "classification", + "debug_log": "automl_errors.log", + # "experiment_exit_score": 0.9, + "enable_early_stopping": False, + "allowed_models": ["LogisticRegression", "SGD", "SVM"], + "iterations": 3, + "iteration_timeout_minutes": 2, + "max_concurrent_iterations": 2, + "max_cores_per_iteration": -1, + "n_cross_validations": 5, + "primary_metric": "accuracy", + "featurization": "off", + "model_explainability": False, + "enable_voting_ensemble": False, + "enable_stack_ensemble": False, + } + + # Init experiment and compute + workspace, experiment = _init_experiment( + context=context, experiment_name=experiment_name + ) + + compute_target = init_compute( + context=context, + cpu_cluster_name=cpu_cluster_name, + vm_size=vm_size, + max_nodes=max_nodes, + ) + + # Register dataset + register_dataset( + context=context, + dataset_name=dataset_name, + dataset_description=dataset_description, + data=dataset, + create_new_version=create_new_version, + ) + + # Submit training job + submit_training_job( + context, + experiment=experiment, + compute_target=compute_target, + register_model_name=register_model_name, + registered_dataset_name=dataset_name, + label_column_name=label_column_name, + automl_settings=automl_settings, + training_set=dataset, + show_output=log_azure, + save_n_models=save_n_models, + )
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/static/documentation.html b/functions/master/azureml_utils/1.4.0/static/documentation.html new file mode 100644 index 00000000..ae451127 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/documentation.html @@ -0,0 +1,361 @@ + + + + + + + +azureml_utils package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

azureml_utils package#

+
+

Submodules#

+
+
+

azureml_utils.azureml_utils module#

+
+
+azureml_utils.azureml_utils.download_model(context: MLClientCtx, model_name: str, model_version: int, target_dir: str = '.') None[source]#
+

Download trained model from Azure ML to local filesystem.

+
+
Parameters:
+
    +
  • context – MLRun context.

  • +
  • model_name – Name of trained and registered model.

  • +
  • model_version – Version of model to download.

  • +
  • target_dir – Target directory to download model.

  • +
+
+
+
+
+
+azureml_utils.azureml_utils.init_compute(context: MLClientCtx, cpu_cluster_name: str, vm_size: str = 'STANDARD_D2_V2', max_nodes: int = 1) azureml.core.compute.ComputeTarget[source]#
+

Initialize Azure ML compute target to run experiment. Checks for +existing compute target and creates new if does not exist.

+
+
Parameters:
+
    +
  • context – MLRun context.

  • +
  • cpu_cluster_name – Name of Azure ML compute target. Created if does not exist.

  • +
  • vm_size – Azure machine type for compute target.

  • +
  • max_nodes – Maximum number of concurrent compute targets.

  • +
+
+
Returns:
+

Azure ML Compute Target.

+
+
+
+
+
+azureml_utils.azureml_utils.register_dataset(context: MLClientCtx, dataset_name: str, dataset_description: str, data: DataItem, create_new_version: bool = False)[source]#
+

Register dataset object (can be also an Iguazio FeatureVector) in Azure ML. +Uploads parquet file to Azure blob storage and registers +that file as a dataset in Azure ML.

+
+
Parameters:
+
    +
  • context – MLRun context.

  • +
  • dataset_name – Name of Azure dataset to register.

  • +
  • dataset_description – Description of Azure dataset to register.

  • +
  • data – MLRun FeatureVector or dataset object to upload.

  • +
  • create_new_version – Register Azure dataset as new version. Must be used when +modifying dataset schema.

  • +
+
+
+
+
+
+azureml_utils.azureml_utils.submit_training_job(context: MLClientCtx, experiment: azureml.core.experiment.Experiment, compute_target: azureml.core.compute.ComputeTarget, register_model_name: str, registered_dataset_name: str, automl_settings: dict, training_set: DataItem, label_column_name: str = '', save_n_models: int = 3, show_output: bool = True) None[source]#
+

Submit training job to Azure AutoML and download trained model +when completed. Uses previously registered dataset for training.

+
+
Parameters:
+
    +
  • context – MLRun context.

  • +
  • experiment – Azure experiment.

  • +
  • compute_target – Azure compute target.

  • +
  • register_model_name – Name of model to register in Azure.

  • +
  • registered_dataset_name – Name of dataset registered in Azure ML.

  • +
  • label_column_name – Name of target column in dataset.

  • +
  • automl_settings – JSON string of all Azure AutoML settings.

  • +
  • training_set – Training set to log with model. For model +monitoring integration.

  • +
  • show_output – Displaying Azure logs.

  • +
  • save_n_models – How many of the top performing models to log.

  • +
+
+
+
+
+
+azureml_utils.azureml_utils.train(context: MLClientCtx, dataset: DataItem, experiment_name: str = '', cpu_cluster_name: str = '', vm_size: str = 'STANDARD_D2_V2', max_nodes: int = 1, dataset_name: str = '', dataset_description: str = '', create_new_version: bool = False, label_column_name: str = '', register_model_name: str = '', save_n_models: int = 1, log_azure: bool = True, automl_settings: str | None = None) None[source]#
+

Whole training flow for Azure AutoML. Registers dataset/feature vector, +submits training job to Azure AutoML, and downloads trained model +when completed.

+
+
Parameters:
+
    +
  • context – MLRun context.

  • +
  • dataset – MLRun FeatureVector or dataset URI to upload. Will drop +index before uploading when it is a FeatureVector.

  • +
  • experiment_name – Name of experiment to create in Azure ML.

  • +
  • cpu_cluster_name – Name of Azure ML compute target. Created if does not exist.

  • +
  • vm_size – Azure machine type for compute target.

  • +
  • max_nodes – Maximum number of concurrent compute targets.

  • +
  • dataset_name – Name of Azure dataset to register.

  • +
  • dataset_description – Description of Azure dataset to register.

  • +
  • create_new_version – Register Azure dataset as new version. Must be used when +modifying dataset schema.

  • +
  • label_column_name – Target column in dataset.

  • +
  • register_model_name – Name of model to register in Azure.

  • +
  • save_n_models – How many of the top performing models to log.

  • +
  • log_azure – Displaying Azure logs.

  • +
  • automl_settings – JSON string of all Azure AutoML settings.

  • +
+
+
+
+
+
+azureml_utils.azureml_utils.upload_model(context: MLClientCtx, model_name: str, model_path: str, model_description: str | None = None, model_tags: dict | None = None) None[source]#
+

Upload pre-trained model from local filesystem to Azure ML. +:param context: MLRun context. +:param model_name: Name of trained and registered model. +:param model_path: Path to file on local filesystem. +:param model_description: Description of models. +:param model_tags: KV pairs of model tags.

+
+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/static/example.html b/functions/master/azureml_utils/1.4.0/static/example.html new file mode 100644 index 00000000..13e6a910 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/example.html @@ -0,0 +1,1661 @@ + + + + + + + +AzureML AutoML Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

AzureML AutoML Demo#

+

MLRun function for using Azure AutoML, Including the following handlers:

+
    +
  1. init_experiment - Initialize workspace and experiment in Azure ML.

  2. +
  3. init_compute - Initialize Azure ML compute target to run experiment.

  4. +
  5. register_dataset - Register dataset object (can be also an Iguazio FeatureVector) in Azure ML.

  6. +
  7. download_model - Download trained model from Azure ML to local filesystem.

  8. +
  9. upload_model - Upload pre-trained model from local filesystem to Azure ML.

  10. +
  11. submit_training_job - Submit training job to Azure AutoML and download trained model when completed.

  12. +
  13. automl_train - Whole training flow for Azure AutoML: +- Initializing workspace and experiment in Azure ML +- Registers dataset/feature vector, +- submits training job +- downloads trained model

  14. +
+
+

1. Setup MLRun Project#

+

Creating MLRun project

+
+
+
import mlrun
+
+
+
+
+
> 2022-02-02 18:28:06,840 [warning] Failed resolving version info. Ignoring and using defaults
+> 2022-02-02 18:28:11,379 [warning] Server or client version is unstable. Assuming compatible: {'server_version': '0.0.0+unstable', 'client_version': '0.0.0+unstable'}
+
+
+
+
+
+
+
# Initialize the MLRun project object
+project = mlrun.get_or_create_project('azureml', context="./", user_project=True)
+
+
+
+
+
> 2022-02-02 18:28:11,423 [info] loaded project azureml from MLRun DB
+
+
+
+
+
+
+

2. Preparing Dataset (Iris)#

+
    +
  • Preparing training URI for the MLRun function

  • +
+
+
+
DATA_URL = "https://s3.wasabisys.com/iguazio/data/iris/iris_dataset.csv"
+
+mlrun.get_dataitem(DATA_URL).as_df().head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)label
05.13.51.40.20
14.93.01.40.20
24.73.21.30.20
34.63.11.50.20
45.03.61.40.20
+
+
+
+
+

3. Submit Azure AutoML Training Job#

+
+

Submit Azure Secrets#

+

For more information about working with secrets see: MLRun docs: Working with secrets

+
+
+
project.set_secrets(file_path="env")
+
+
+
+
+
+
+

Import azureml_utils from marketplace#

+
+
+
azureml_fn = mlrun.import_function('hub://azureml_utils')
+azureml_fn.deploy()
+
+
+
+
+
+
+

Automl configuration & run parameters#

+
    +
  • The automl_settings object is the setup for Azure AutoML. It holds the task type, number of models to train - iterations, the desired metric - primary metric, the allowed types of models allowed_models and more.

  • +
  • The params are the parameters for the MLRun function, such as experiment (experiment_name) and cpu cluster (cpu_cluster_name) names in AzureML, dataset properties for registration, target label for training - label_column_name, number of models to download save_n_models and more.

  • +
+
+
+
label_column_name = 'label' # target label
+
+# Configure automl settings:
+automl_settings = {
+            "task": 'classification',
+            "debug_log": 'automl_errors.log',
+#             "experiment_exit_score" : 0.9,
+            "enable_early_stopping": False,
+            "allowed_models": ['LogisticRegression', 'SGD', 'SVM'],
+            "iterations": 5,
+            "iteration_timeout_minutes": 2,
+            "max_concurrent_iterations": 2,
+            "max_cores_per_iteration": -1,
+            "n_cross_validations": 5,
+            "primary_metric": 'accuracy',
+            "featurization": 'off',
+            "model_explainability": False,
+            "enable_voting_ensemble": False,
+            "enable_stack_ensemble": False
+        }
+
+# Setting params to azure_run function:
+params = {
+    "experiment_name": 'azure-automl-test',
+    "cpu_cluster_name": 'azureml-cpu',
+    "dataset_name": 'iris',
+    "dataset_description": 'iris training data',
+    "label_column_name": label_column_name,
+    "create_new_version": True,
+    "register_model_name": "iris-model",
+    "save_n_models": 3,
+    "automl_settings": automl_settings
+}
+
+
+
+
+
+
+

Run Azure AutoML train:#

+

This MLRun function will perform the following:

+
    +
  • Initialize workspace and experiment in your AzureML

  • +
  • Register the dataset/feature vector to Iguazio and to AzureML.

  • +
  • Submit the training job to AzureML and print the live training results fro each model

  • +
  • Generate the top trained models.

  • +
+
+
+
azureml_run = azureml_fn.run(
+    handler="train",
+    inputs={"dataset": DATA_URL},
+    params=params,
+)
+
+
+
+
+
> 2022-02-02 18:28:11,740 [info] Function is not deployed and auto_build flag is set, starting deploy...
+> 2022-02-02 18:28:11,932 [info] Started building image: .mlrun/func-azureml-yonatan-azureml-utils:latest
+INFO[0000] Retrieving image manifest python:3.7.9-slim  
+INFO[0000] Retrieving image python:3.7.9-slim from registry index.docker.io 
+INFO[0000] Built cross stage deps: map[]                
+INFO[0000] Retrieving image manifest python:3.7.9-slim  
+INFO[0000] Returning cached image manifest              
+INFO[0000] Executing 0 build triggers                   
+INFO[0000] Unpacking rootfs as cmd RUN python -m pip install pip==21.2.4 requires it. 
+INFO[0002] RUN python -m pip install pip==21.2.4        
+INFO[0002] Taking snapshot of full filesystem...        
+INFO[0003] cmd: /bin/sh                                 
+INFO[0003] args: [-c python -m pip install pip==21.2.4] 
+INFO[0003] Running: [/bin/sh -c python -m pip install pip==21.2.4] 
+Collecting pip==21.2.4
+  Downloading pip-21.2.4-py3-none-any.whl (1.6 MB)
+Installing collected packages: pip
+  Attempting uninstall: pip
+    Found existing installation: pip 21.0.1
+    Uninstalling pip-21.0.1:
+      Successfully uninstalled pip-21.0.1
+Successfully installed pip-21.2.4
+INFO[0006] Taking snapshot of full filesystem...        
+INFO[0006] RUN apt-get update && apt-get install -y --no-install-recommends git 
+INFO[0006] cmd: /bin/sh                                 
+INFO[0006] args: [-c apt-get update && apt-get install -y --no-install-recommends git] 
+INFO[0006] Running: [/bin/sh -c apt-get update && apt-get install -y --no-install-recommends git] 
+Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB]
+Get:2 http://deb.debian.org/debian buster InRelease [122 kB]
+Get:3 http://deb.debian.org/debian buster-updates InRelease [51.9 kB]
+Get:4 http://security.debian.org/debian-security buster/updates/main amd64 Packages [314 kB]
+Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7906 kB]
+Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [8792 B]
+Fetched 8468 kB in 2s (5347 kB/s)
+Reading package lists...
+Reading package lists...
+Building dependency tree...
+Reading state information...
+The following additional packages will be installed:
+  git-man libcurl3-gnutls liberror-perl libgdbm-compat4 libgssapi-krb5-2
+  libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2
+  libldap-common libnghttp2-14 libpcre2-8-0 libperl5.28 libpsl5 librtmp1
+  libsasl2-2 libsasl2-modules-db libssh2-1 perl perl-modules-5.28
+Suggested packages:
+  gettext-base git-daemon-run | git-daemon-sysvinit git-doc git-el git-email
+  git-gui gitk gitweb git-cvs git-mediawiki git-svn krb5-doc krb5-user
+  sensible-utils perl-doc libterm-readline-gnu-perl
+  | libterm-readline-perl-perl make libb-debug-perl liblocale-codes-perl
+Recommended packages:
+  patch less ssh-client krb5-locales publicsuffix libsasl2-modules
+The following NEW packages will be installed:
+  git git-man libcurl3-gnutls liberror-perl libgdbm-compat4 libgssapi-krb5-2
+  libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2
+  libldap-common libnghttp2-14 libpcre2-8-0 libperl5.28 libpsl5 librtmp1
+  libsasl2-2 libsasl2-modules-db libssh2-1 perl perl-modules-5.28
+0 upgraded, 22 newly installed, 0 to remove and 16 not upgraded.
+Need to get 16.4 MB of archives.
+After this operation, 90.1 MB of additional disk space will be used.
+Get:1 http://deb.debian.org/debian buster/main amd64 perl-modules-5.28 all 5.28.1-6+deb10u1 [2873 kB]
+Get:2 http://deb.debian.org/debian buster/main amd64 libgdbm-compat4 amd64 1.18.1-4 [44.1 kB]
+Get:3 http://deb.debian.org/debian buster/main amd64 libperl5.28 amd64 5.28.1-6+deb10u1 [3894 kB]
+Get:4 http://deb.debian.org/debian buster/main amd64 perl amd64 5.28.1-6+deb10u1 [204 kB]
+Get:5 http://deb.debian.org/debian buster/main amd64 libkeyutils1 amd64 1.6-6 [15.0 kB]
+Get:6 http://deb.debian.org/debian buster/main amd64 libkrb5support0 amd64 1.17-3+deb10u3 [65.8 kB]
+Get:7 http://deb.debian.org/debian buster/main amd64 libk5crypto3 amd64 1.17-3+deb10u3 [122 kB]
+Get:8 http://deb.debian.org/debian buster/main amd64 libkrb5-3 amd64 1.17-3+deb10u3 [370 kB]
+Get:9 http://deb.debian.org/debian buster/main amd64 libgssapi-krb5-2 amd64 1.17-3+deb10u3 [158 kB]
+Get:10 http://deb.debian.org/debian buster/main amd64 libsasl2-modules-db amd64 2.1.27+dfsg-1+deb10u1 [69.1 kB]
+Get:11 http://deb.debian.org/debian buster/main amd64 libsasl2-2 amd64 2.1.27+dfsg-1+deb10u1 [106 kB]
+Get:12 http://deb.debian.org/debian buster/main amd64 libldap-common all 2.4.47+dfsg-3+deb10u6 [90.0 kB]
+Get:13 http://deb.debian.org/debian buster/main amd64 libldap-2.4-2 amd64 2.4.47+dfsg-3+deb10u6 [224 kB]
+Get:14 http://deb.debian.org/debian buster/main amd64 libnghttp2-14 amd64 1.36.0-2+deb10u1 [85.0 kB]
+Get:15 http://deb.debian.org/debian buster/main amd64 libpsl5 amd64 0.20.2-2 [53.7 kB]
+Get:16 http://deb.debian.org/debian buster/main amd64 librtmp1 amd64 2.4+20151223.gitfa8646d.1-2 [60.5 kB]
+Get:17 http://deb.debian.org/debian buster/main amd64 libssh2-1 amd64 1.8.0-2.1 [140 kB]
+Get:18 http://deb.debian.org/debian buster/main amd64 libcurl3-gnutls amd64 7.64.0-4+deb10u2 [330 kB]
+Get:19 http://deb.debian.org/debian buster/main amd64 libpcre2-8-0 amd64 10.32-5 [213 kB]
+Get:20 http://deb.debian.org/debian buster/main amd64 liberror-perl all 0.17027-2 [30.9 kB]
+Get:21 http://deb.debian.org/debian buster/main amd64 git-man all 1:2.20.1-2+deb10u3 [1620 kB]
+Get:22 http://deb.debian.org/debian buster/main amd64 git amd64 1:2.20.1-2+deb10u3 [5633 kB]
+debconf: delaying package configuration, since apt-utils is not installed
+Fetched 16.4 MB in 0s (62.3 MB/s)
+Selecting previously unselected package perl-modules-5.28.
+(Reading database ... 6840 files and directories currently installed.)
+Preparing to unpack .../00-perl-modules-5.28_5.28.1-6+deb10u1_all.deb ...
+Unpacking perl-modules-5.28 (5.28.1-6+deb10u1) ...
+Selecting previously unselected package libgdbm-compat4:amd64.
+Preparing to unpack .../01-libgdbm-compat4_1.18.1-4_amd64.deb ...
+Unpacking libgdbm-compat4:amd64 (1.18.1-4) ...
+Selecting previously unselected package libperl5.28:amd64.
+Preparing to unpack .../02-libperl5.28_5.28.1-6+deb10u1_amd64.deb ...
+Unpacking libperl5.28:amd64 (5.28.1-6+deb10u1) ...
+Selecting previously unselected package perl.
+Preparing to unpack .../03-perl_5.28.1-6+deb10u1_amd64.deb ...
+Unpacking perl (5.28.1-6+deb10u1) ...
+Selecting previously unselected package libkeyutils1:amd64.
+Preparing to unpack .../04-libkeyutils1_1.6-6_amd64.deb ...
+Unpacking libkeyutils1:amd64 (1.6-6) ...
+Selecting previously unselected package libkrb5support0:amd64.
+Preparing to unpack .../05-libkrb5support0_1.17-3+deb10u3_amd64.deb ...
+Unpacking libkrb5support0:amd64 (1.17-3+deb10u3) ...
+Selecting previously unselected package libk5crypto3:amd64.
+Preparing to unpack .../06-libk5crypto3_1.17-3+deb10u3_amd64.deb ...
+Unpacking libk5crypto3:amd64 (1.17-3+deb10u3) ...
+Selecting previously unselected package libkrb5-3:amd64.
+Preparing to unpack .../07-libkrb5-3_1.17-3+deb10u3_amd64.deb ...
+Unpacking libkrb5-3:amd64 (1.17-3+deb10u3) ...
+Selecting previously unselected package libgssapi-krb5-2:amd64.
+Preparing to unpack .../08-libgssapi-krb5-2_1.17-3+deb10u3_amd64.deb ...
+Unpacking libgssapi-krb5-2:amd64 (1.17-3+deb10u3) ...
+Selecting previously unselected package libsasl2-modules-db:amd64.
+Preparing to unpack .../09-libsasl2-modules-db_2.1.27+dfsg-1+deb10u1_amd64.deb ...
+Unpacking libsasl2-modules-db:amd64 (2.1.27+dfsg-1+deb10u1) ...
+Selecting previously unselected package libsasl2-2:amd64.
+Preparing to unpack .../10-libsasl2-2_2.1.27+dfsg-1+deb10u1_amd64.deb ...
+Unpacking libsasl2-2:amd64 (2.1.27+dfsg-1+deb10u1) ...
+Selecting previously unselected package libldap-common.
+Preparing to unpack .../11-libldap-common_2.4.47+dfsg-3+deb10u6_all.deb ...
+Unpacking libldap-common (2.4.47+dfsg-3+deb10u6) ...
+Selecting previously unselected package libldap-2.4-2:amd64.
+Preparing to unpack .../12-libldap-2.4-2_2.4.47+dfsg-3+deb10u6_amd64.deb ...
+Unpacking libldap-2.4-2:amd64 (2.4.47+dfsg-3+deb10u6) ...
+Selecting previously unselected package libnghttp2-14:amd64.
+Preparing to unpack .../13-libnghttp2-14_1.36.0-2+deb10u1_amd64.deb ...
+Unpacking libnghttp2-14:amd64 (1.36.0-2+deb10u1) ...
+Selecting previously unselected package libpsl5:amd64.
+Preparing to unpack .../14-libpsl5_0.20.2-2_amd64.deb ...
+Unpacking libpsl5:amd64 (0.20.2-2) ...
+Selecting previously unselected package librtmp1:amd64.
+Preparing to unpack .../15-librtmp1_2.4+20151223.gitfa8646d.1-2_amd64.deb ...
+Unpacking librtmp1:amd64 (2.4+20151223.gitfa8646d.1-2) ...
+Selecting previously unselected package libssh2-1:amd64.
+Preparing to unpack .../16-libssh2-1_1.8.0-2.1_amd64.deb ...
+Unpacking libssh2-1:amd64 (1.8.0-2.1) ...
+Selecting previously unselected package libcurl3-gnutls:amd64.
+Preparing to unpack .../17-libcurl3-gnutls_7.64.0-4+deb10u2_amd64.deb ...
+Unpacking libcurl3-gnutls:amd64 (7.64.0-4+deb10u2) ...
+Selecting previously unselected package libpcre2-8-0:amd64.
+Preparing to unpack .../18-libpcre2-8-0_10.32-5_amd64.deb ...
+Unpacking libpcre2-8-0:amd64 (10.32-5) ...
+Selecting previously unselected package liberror-perl.
+Preparing to unpack .../19-liberror-perl_0.17027-2_all.deb ...
+Unpacking liberror-perl (0.17027-2) ...
+Selecting previously unselected package git-man.
+Preparing to unpack .../20-git-man_1%3a2.20.1-2+deb10u3_all.deb ...
+Unpacking git-man (1:2.20.1-2+deb10u3) ...
+Selecting previously unselected package git.
+Preparing to unpack .../21-git_1%3a2.20.1-2+deb10u3_amd64.deb ...
+Unpacking git (1:2.20.1-2+deb10u3) ...
+Setting up perl-modules-5.28 (5.28.1-6+deb10u1) ...
+Setting up libkeyutils1:amd64 (1.6-6) ...
+Setting up libpsl5:amd64 (0.20.2-2) ...
+Setting up libnghttp2-14:amd64 (1.36.0-2+deb10u1) ...
+Setting up libldap-common (2.4.47+dfsg-3+deb10u6) ...
+Setting up libkrb5support0:amd64 (1.17-3+deb10u3) ...
+Setting up libsasl2-modules-db:amd64 (2.1.27+dfsg-1+deb10u1) ...
+Setting up librtmp1:amd64 (2.4+20151223.gitfa8646d.1-2) ...
+Setting up libgdbm-compat4:amd64 (1.18.1-4) ...
+Setting up libpcre2-8-0:amd64 (10.32-5) ...
+Setting up libk5crypto3:amd64 (1.17-3+deb10u3) ...
+Setting up libsasl2-2:amd64 (2.1.27+dfsg-1+deb10u1) ...
+Setting up libperl5.28:amd64 (5.28.1-6+deb10u1) ...
+Setting up git-man (1:2.20.1-2+deb10u3) ...
+Setting up libssh2-1:amd64 (1.8.0-2.1) ...
+Setting up libkrb5-3:amd64 (1.17-3+deb10u3) ...
+Setting up libldap-2.4-2:amd64 (2.4.47+dfsg-3+deb10u6) ...
+Setting up perl (5.28.1-6+deb10u1) ...
+Setting up libgssapi-krb5-2:amd64 (1.17-3+deb10u3) ...
+Setting up libcurl3-gnutls:amd64 (7.64.0-4+deb10u2) ...
+Setting up liberror-perl (0.17027-2) ...
+Setting up git (1:2.20.1-2+deb10u3) ...
+Processing triggers for libc-bin (2.28-10) ...
+INFO[0012] Taking snapshot of full filesystem...        
+INFO[0015] RUN python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0 
+INFO[0015] cmd: /bin/sh                                 
+INFO[0015] args: [-c python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0] 
+INFO[0015] Running: [/bin/sh -c python -m pip install azureml-core==1.33.0 azureml-train-automl-client==1.33.0] 
+Collecting azureml-core==1.33.0
+  Downloading azureml_core-1.33.0-py3-none-any.whl (2.2 MB)
+Collecting azureml-train-automl-client==1.33.0
+  Downloading azureml_train_automl_client-1.33.0-py3-none-any.whl (128 kB)
+Collecting pytz
+  Downloading pytz-2021.3-py2.py3-none-any.whl (503 kB)
+Collecting urllib3<=1.26.5,>=1.23
+  Downloading urllib3-1.26.5-py2.py3-none-any.whl (138 kB)
+Collecting pathspec<1.0.0
+  Downloading pathspec-0.9.0-py2.py3-none-any.whl (31 kB)
+Collecting azure-mgmt-containerregistry>=2.0.0
+  Downloading azure_mgmt_containerregistry-9.0.0-py3-none-any.whl (937 kB)
+Collecting contextlib2<1.0.0
+  Downloading contextlib2-0.6.0.post1-py2.py3-none-any.whl (9.8 kB)
+Collecting SecretStorage<4.0.0
+  Downloading SecretStorage-3.3.1-py3-none-any.whl (15 kB)
+Collecting backports.tempfile
+  Downloading backports.tempfile-1.0-py2.py3-none-any.whl (4.4 kB)
+Collecting docker<5.0.0
+  Downloading docker-4.4.4-py2.py3-none-any.whl (147 kB)
+Collecting msrestazure<=0.6.4,>=0.4.33
+  Downloading msrestazure-0.6.4-py2.py3-none-any.whl (40 kB)
+Collecting cryptography!=1.9,!=2.0.*,!=2.1.*,!=2.2.*,<4.0.0
+  Downloading cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl (3.0 MB)
+Collecting msrest<1.0.0,>=0.5.1
+  Downloading msrest-0.6.21-py2.py3-none-any.whl (85 kB)
+Collecting requests<3.0.0,>=2.19.1
+  Downloading requests-2.27.1-py2.py3-none-any.whl (63 kB)
+Collecting adal<=1.2.7,>=1.2.0
+  Downloading adal-1.2.7-py2.py3-none-any.whl (55 kB)
+Collecting jsonpickle<3.0.0
+  Downloading jsonpickle-2.1.0-py2.py3-none-any.whl (38 kB)
+Collecting azure-common<2.0.0,>=1.1.12
+  Downloading azure_common-1.1.27-py2.py3-none-any.whl (12 kB)
+Collecting python-dateutil<3.0.0,>=2.7.3
+  Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
+Collecting PyJWT<3.0.0
+  Downloading PyJWT-2.3.0-py3-none-any.whl (16 kB)
+Collecting azure-mgmt-authorization<1.0.0,>=0.40.0
+  Downloading azure_mgmt_authorization-0.61.0-py2.py3-none-any.whl (94 kB)
+Collecting pyopenssl<21.0.0
+  Downloading pyOpenSSL-20.0.1-py2.py3-none-any.whl (54 kB)
+Collecting jmespath<1.0.0
+  Downloading jmespath-0.10.0-py2.py3-none-any.whl (24 kB)
+Collecting ndg-httpsclient<=0.5.1
+  Downloading ndg_httpsclient-0.5.1-py3-none-any.whl (34 kB)
+Collecting azure-mgmt-storage<16.0.0,>=1.5.0
+  Downloading azure_mgmt_storage-11.2.0-py2.py3-none-any.whl (547 kB)
+Collecting azure-mgmt-keyvault<10.0.0,>=0.40.0
+  Downloading azure_mgmt_keyvault-9.3.0-py2.py3-none-any.whl (412 kB)
+Collecting ruamel.yaml<0.17.5,>=0.15.35
+  Downloading ruamel.yaml-0.17.4-py3-none-any.whl (101 kB)
+Collecting azure-graphrbac<1.0.0,>=0.40.0
+  Downloading azure_graphrbac-0.61.1-py2.py3-none-any.whl (141 kB)
+Collecting azure-mgmt-resource<15.0.0,>=1.2.1
+  Downloading azure_mgmt_resource-13.0.0-py2.py3-none-any.whl (1.3 MB)
+Collecting azureml-dataset-runtime~=1.33.0
+  Downloading azureml_dataset_runtime-1.33.0-py3-none-any.whl (3.5 kB)
+Collecting azureml-telemetry~=1.33.0
+  Downloading azureml_telemetry-1.33.0-py3-none-any.whl (30 kB)
+Collecting azureml-automl-core~=1.33.0
+  Downloading azureml_automl_core-1.33.1-py3-none-any.whl (214 kB)
+Collecting azure-mgmt-core<2.0.0,>=1.3.0
+  Downloading azure_mgmt_core-1.3.0-py2.py3-none-any.whl (25 kB)
+Collecting azure-core<2.0.0,>=1.15.0
+  Downloading azure_core-1.21.1-py2.py3-none-any.whl (178 kB)
+Collecting six>=1.11.0
+  Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
+Collecting pyarrow<4.0.0,>=0.17.0
+  Downloading pyarrow-3.0.0-cp37-cp37m-manylinux2014_x86_64.whl (20.7 MB)
+Collecting azureml-dataprep<2.21.0a,>=2.20.0a
+  Downloading azureml_dataprep-2.20.1-py3-none-any.whl (39.4 MB)
+Collecting numpy!=1.19.3
+  Downloading numpy-1.21.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.7 MB)
+Collecting azure-identity<1.5.0,>=1.2.0
+  Downloading azure_identity-1.4.1-py2.py3-none-any.whl (86 kB)
+Collecting dotnetcore2<3.0.0,>=2.1.14
+  Downloading dotnetcore2-2.1.23-py3-none-manylinux1_x86_64.whl (29.3 MB)
+Collecting azureml-dataprep-native<39.0.0,>=38.0.0
+  Downloading azureml_dataprep_native-38.0.0-cp37-cp37m-manylinux1_x86_64.whl (1.3 MB)
+Collecting azureml-dataprep-rslex<1.19.0a,>=1.18.0dev0
+  Downloading azureml_dataprep_rslex-1.18.2-cp37-cp37m-manylinux1_x86_64.whl (10.4 MB)
+Collecting cloudpickle<2.0.0,>=1.1.0
+  Downloading cloudpickle-1.6.0-py3-none-any.whl (23 kB)
+Collecting msal-extensions~=0.2.2
+  Downloading msal_extensions-0.2.2-py2.py3-none-any.whl (15 kB)
+Collecting msal<2.0.0,>=1.3.0
+  Downloading msal-1.16.0-py2.py3-none-any.whl (78 kB)
+Collecting applicationinsights
+  Downloading applicationinsights-0.11.10-py2.py3-none-any.whl (55 kB)
+Collecting cffi>=1.12
+  Downloading cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (427 kB)
+Collecting pycparser
+  Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
+Collecting websocket-client>=0.32.0
+  Downloading websocket_client-1.2.3-py3-none-any.whl (53 kB)
+Collecting distro>=1.2.0
+  Downloading distro-1.6.0-py2.py3-none-any.whl (19 kB)
+Collecting importlib-metadata
+  Downloading importlib_metadata-4.10.1-py3-none-any.whl (17 kB)
+Collecting portalocker~=1.0
+  Downloading portalocker-1.7.1-py2.py3-none-any.whl (10 kB)
+Collecting requests-oauthlib>=0.5.0
+  Downloading requests_oauthlib-1.3.1-py2.py3-none-any.whl (23 kB)
+Collecting isodate>=0.6.0
+  Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)
+Collecting certifi>=2017.4.17
+  Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
+Collecting pyasn1>=0.1.1
+  Downloading pyasn1-0.4.8-py2.py3-none-any.whl (77 kB)
+Collecting charset-normalizer~=2.0.0
+  Downloading charset_normalizer-2.0.11-py3-none-any.whl (39 kB)
+Collecting idna<4,>=2.5
+  Downloading idna-3.3-py3-none-any.whl (61 kB)
+Collecting oauthlib>=3.0.0
+  Downloading oauthlib-3.2.0-py3-none-any.whl (151 kB)
+Collecting ruamel.yaml.clib>=0.1.2
+  Downloading ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl (546 kB)
+Collecting jeepney>=0.6
+  Downloading jeepney-0.7.1-py3-none-any.whl (54 kB)
+Collecting backports.weakref
+  Downloading backports.weakref-1.0.post1-py2.py3-none-any.whl (5.2 kB)
+Collecting zipp>=0.5
+  Downloading zipp-3.7.0-py3-none-any.whl (5.3 kB)
+Collecting typing-extensions>=3.6.4
+  Downloading typing_extensions-4.0.1-py3-none-any.whl (22 kB)
+Installing collected packages: pycparser, urllib3, idna, charset-normalizer, cffi, certifi, six, requests, PyJWT, oauthlib, cryptography, requests-oauthlib, python-dateutil, isodate, zipp, typing-extensions, portalocker, msrest, msal, azure-core, adal, websocket-client, ruamel.yaml.clib, pyopenssl, pyasn1, msrestazure, msal-extensions, jeepney, importlib-metadata, distro, backports.weakref, azure-mgmt-core, azure-common, SecretStorage, ruamel.yaml, pytz, pathspec, numpy, ndg-httpsclient, jsonpickle, jmespath, dotnetcore2, docker, contextlib2, cloudpickle, backports.tempfile, azureml-dataprep-rslex, azureml-dataprep-native, azure-mgmt-storage, azure-mgmt-resource, azure-mgmt-keyvault, azure-mgmt-containerregistry, azure-mgmt-authorization, azure-identity, azure-graphrbac, pyarrow, azureml-dataprep, azureml-core, applicationinsights, azureml-telemetry, azureml-dataset-runtime, azureml-automl-core, azureml-train-automl-client
+Successfully installed PyJWT-2.3.0 SecretStorage-3.3.1 adal-1.2.7 applicationinsights-0.11.10 azure-common-1.1.27 azure-core-1.21.1 azure-graphrbac-0.61.1 azure-identity-1.4.1 azure-mgmt-authorization-0.61.0 azure-mgmt-containerregistry-9.0.0 azure-mgmt-core-1.3.0 azure-mgmt-keyvault-9.3.0 azure-mgmt-resource-13.0.0 azure-mgmt-storage-11.2.0 azureml-automl-core-1.33.1 azureml-core-1.33.0 azureml-dataprep-2.20.1 azureml-dataprep-native-38.0.0 azureml-dataprep-rslex-1.18.2 azureml-dataset-runtime-1.33.0 azureml-telemetry-1.33.0 azureml-train-automl-client-1.33.0 backports.tempfile-1.0 backports.weakref-1.0.post1 certifi-2021.10.8 cffi-1.15.0 charset-normalizer-2.0.11 cloudpickle-1.6.0 contextlib2-0.6.0.post1 cryptography-3.4.8 distro-1.6.0 docker-4.4.4 dotnetcore2-2.1.23 idna-3.3 importlib-metadata-4.10.1 isodate-0.6.1 jeepney-0.7.1 jmespath-0.10.0 jsonpickle-2.1.0 msal-1.16.0 msal-extensions-0.2.2 msrest-0.6.21 msrestazure-0.6.4 ndg-httpsclient-0.5.1 numpy-1.21.5 oauthlib-3.2.0 pathspec-0.9.0 portalocker-1.7.1 pyarrow-3.0.0 pyasn1-0.4.8 pycparser-2.21 pyopenssl-20.0.1 python-dateutil-2.8.2 pytz-2021.3 requests-2.27.1 requests-oauthlib-1.3.1 ruamel.yaml-0.17.4 ruamel.yaml.clib-0.2.6 six-1.16.0 typing-extensions-4.0.1 urllib3-1.26.5 websocket-client-1.2.3 zipp-3.7.0
+WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
+WARNING: You are using pip version 21.2.4; however, version 22.0.2 is available.
+You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
+INFO[0040] Taking snapshot of full filesystem...        
+INFO[0059] RUN python -m pip install "mlrun[complete] @ git+https://github.com/mlrun/mlrun@development" 
+INFO[0059] cmd: /bin/sh                                 
+INFO[0059] args: [-c python -m pip install "mlrun[complete] @ git+https://github.com/mlrun/mlrun@development"] 
+INFO[0059] Running: [/bin/sh -c python -m pip install "mlrun[complete] @ git+https://github.com/mlrun/mlrun@development"] 
+Collecting mlrun[complete]@ git+https://github.com/mlrun/mlrun@development
+  Cloning https://github.com/mlrun/mlrun (to revision development) to /tmp/pip-install-yegk19ip/mlrun_b9fdf6ee5c8c4e2d9de3a37d0807b6c5
+  Running command git clone -q https://github.com/mlrun/mlrun /tmp/pip-install-yegk19ip/mlrun_b9fdf6ee5c8c4e2d9de3a37d0807b6c5
+  Resolved https://github.com/mlrun/mlrun to commit 832a07b11f3198b844d30b4a80db12a45c6e8948
+  Installing build dependencies: started
+  Installing build dependencies: finished with status 'done'
+  Getting requirements to build wheel: started
+  Getting requirements to build wheel: finished with status 'done'
+    Preparing wheel metadata: started
+    Preparing wheel metadata: finished with status 'done'
+Collecting pymysql~=1.0
+  Downloading PyMySQL-1.0.2-py3-none-any.whl (43 kB)
+Requirement already satisfied: pyarrow<6,>=1 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.0.0)
+Collecting fsspec~=2021.8.1
+  Downloading fsspec-2021.8.1-py3-none-any.whl (119 kB)
+Collecting v3io~=0.5.13
+  Downloading v3io-0.5.15-py3-none-any.whl (49 kB)
+Collecting ipykernel~=5.0
+  Downloading ipykernel-5.5.6-py3-none-any.whl (121 kB)
+Collecting click~=7.0
+  Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
+Collecting sqlalchemy~=1.3
+  Downloading SQLAlchemy-1.4.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.6 MB)
+Collecting dask~=2021.11.2
+  Downloading dask-2021.11.2-py3-none-any.whl (1.0 MB)
+Collecting distributed~=2021.11.2
+  Downloading distributed-2021.11.2-py3-none-any.whl (802 kB)
+Collecting chardet<4.0,>=3.0.2
+  Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
+Collecting nuclio-jupyter~=0.8.22
+  Downloading nuclio_jupyter-0.8.22-py3-none-any.whl (49 kB)
+Collecting pydantic~=1.5
+  Downloading pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.9 MB)
+Collecting v3iofs~=0.1.7
+  Downloading v3iofs-0.1.10-py3-none-any.whl (13 kB)
+Collecting kfp~=1.8.0
+  Downloading kfp-1.8.11.tar.gz (298 kB)
+Collecting pandas~=1.2
+  Downloading pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.3 MB)
+Collecting pyyaml~=5.1
+  Downloading PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl (636 kB)
+Collecting orjson<3.4,>=3
+  Downloading orjson-3.3.1-cp37-cp37m-manylinux2014_x86_64.whl (208 kB)
+Collecting inflection~=0.5.0
+  Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
+Collecting aiohttp~=3.8
+  Downloading aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.1 MB)
+Collecting humanfriendly~=8.2
+  Downloading humanfriendly-8.2-py2.py3-none-any.whl (86 kB)
+Collecting ipython~=7.0
+  Downloading ipython-7.31.1-py3-none-any.whl (792 kB)
+Collecting fastapi~=0.67.0
+  Downloading fastapi-0.67.0-py3-none-any.whl (51 kB)
+Requirement already satisfied: requests~=2.22 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.27.1)
+Collecting tabulate~=0.8.6
+  Downloading tabulate-0.8.9-py3-none-any.whl (25 kB)
+Collecting nest-asyncio~=1.0
+  Downloading nest_asyncio-1.5.4-py3-none-any.whl (5.1 kB)
+Collecting storey~=0.10.4
+  Downloading storey-0.10.4-py3-none-any.whl (116 kB)
+Requirement already satisfied: urllib3<1.27,>=1.25.4 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.26.5)
+Collecting GitPython~=3.0
+  Downloading GitPython-3.1.26-py3-none-any.whl (180 kB)
+Collecting mergedeep~=1.3
+  Downloading mergedeep-1.3.4-py3-none-any.whl (6.4 kB)
+Collecting semver~=2.13
+  Downloading semver-2.13.0-py2.py3-none-any.whl (12 kB)
+Collecting cryptography<3.4,~=3.0
+  Downloading cryptography-3.3.2-cp36-abi3-manylinux2010_x86_64.whl (2.6 MB)
+Collecting v3io-frames~=0.10.2
+  Downloading v3io_frames-0.10.2-py3-none-any.whl (35 kB)
+Collecting deepdiff~=5.0
+  Downloading deepdiff-5.7.0-py3-none-any.whl (68 kB)
+Requirement already satisfied: numpy<1.22.0,>=1.16.5 in /usr/local/lib/python3.7/site-packages (from mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.21.5)
+Collecting python-dotenv~=0.17.0
+  Downloading python_dotenv-0.17.1-py2.py3-none-any.whl (18 kB)
+Collecting alembic<1.6.0,~=1.4
+  Downloading alembic-1.5.8-py2.py3-none-any.whl (159 kB)
+Collecting kubernetes~=12.0
+  Downloading kubernetes-12.0.1-py2.py3-none-any.whl (1.7 MB)
+Collecting google-auth<2.0dev,>=1.25.0
+  Downloading google_auth-1.35.0-py2.py3-none-any.whl (152 kB)
+Collecting azure-identity~=1.5
+  Downloading azure_identity-1.7.1-py2.py3-none-any.whl (129 kB)
+Collecting aiobotocore~=1.4.0
+  Downloading aiobotocore-1.4.2.tar.gz (52 kB)
+Collecting boto3<1.17.107,~=1.9
+  Downloading boto3-1.17.106-py2.py3-none-any.whl (131 kB)
+Collecting azure-keyvault-secrets~=4.2
+  Downloading azure_keyvault_secrets-4.3.0-py2.py3-none-any.whl (233 kB)
+Collecting s3fs~=2021.8.1
+  Downloading s3fs-2021.8.1-py3-none-any.whl (26 kB)
+Collecting plotly~=5.4
+  Downloading plotly-5.5.0-py2.py3-none-any.whl (26.5 MB)
+Collecting botocore<1.20.107,>=1.20.106
+  Downloading botocore-1.20.106-py2.py3-none-any.whl (7.7 MB)
+Collecting adlfs~=2021.8.1
+  Downloading adlfs-2021.8.2.tar.gz (38 kB)
+Collecting gcsfs~=2021.8.1
+  Downloading gcsfs-2021.8.1-py2.py3-none-any.whl (23 kB)
+Collecting azure-storage-blob~=12.0
+  Downloading azure_storage_blob-12.9.0-py2.py3-none-any.whl (356 kB)
+Requirement already satisfied: azure-core>=1.7.0 in /usr/local/lib/python3.7/site-packages (from adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.21.1)
+Collecting azure-datalake-store<0.1,>=0.0.46
+  Downloading azure_datalake_store-0.0.52-py2.py3-none-any.whl (61 kB)
+Collecting wrapt>=1.10.10
+  Downloading wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (79 kB)
+Collecting aioitertools>=0.5.1
+  Downloading aioitertools-0.8.0-py3-none-any.whl (21 kB)
+Collecting frozenlist>=1.1.1
+  Downloading frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (144 kB)
+Collecting yarl<2.0,>=1.0
+  Downloading yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (271 kB)
+Collecting multidict<7.0,>=4.5
+  Downloading multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (94 kB)
+Collecting aiosignal>=1.1.2
+  Downloading aiosignal-1.2.0-py3-none-any.whl (8.2 kB)
+Requirement already satisfied: typing-extensions>=3.7.4 in /usr/local/lib/python3.7/site-packages (from aiohttp~=3.8->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (4.0.1)
+Collecting async-timeout<5.0,>=4.0.0a3
+  Downloading async_timeout-4.0.2-py3-none-any.whl (5.8 kB)
+Collecting asynctest==0.13.0
+  Downloading asynctest-0.13.0-py3-none-any.whl (26 kB)
+Collecting attrs>=17.3.0
+  Downloading attrs-21.4.0-py2.py3-none-any.whl (60 kB)
+Requirement already satisfied: charset-normalizer<3.0,>=2.0 in /usr/local/lib/python3.7/site-packages (from aiohttp~=3.8->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.0.11)
+Collecting Mako
+  Downloading Mako-1.1.6-py2.py3-none-any.whl (75 kB)
+Collecting python-editor>=0.3
+  Downloading python_editor-1.0.4-py3-none-any.whl (4.9 kB)
+Requirement already satisfied: python-dateutil in /usr/local/lib/python3.7/site-packages (from alembic<1.6.0,~=1.4->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.8.2)
+Requirement already satisfied: six>=1.11.0 in /usr/local/lib/python3.7/site-packages (from azure-core>=1.7.0->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.16.0)
+Requirement already satisfied: cffi in /usr/local/lib/python3.7/site-packages (from azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.15.0)
+Requirement already satisfied: adal>=0.4.2 in /usr/local/lib/python3.7/site-packages (from azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.2.7)
+Requirement already satisfied: PyJWT<3,>=1.0.0 in /usr/local/lib/python3.7/site-packages (from adal>=0.4.2->azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.3.0)
+Requirement already satisfied: msal<2.0.0,>=1.12.0 in /usr/local/lib/python3.7/site-packages (from azure-identity~=1.5->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.16.0)
+Collecting msal-extensions~=0.3.0
+  Downloading msal_extensions-0.3.1-py2.py3-none-any.whl (18 kB)
+Requirement already satisfied: azure-common~=1.1 in /usr/local/lib/python3.7/site-packages (from azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.1.27)
+Requirement already satisfied: msrest>=0.6.21 in /usr/local/lib/python3.7/site-packages (from azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.6.21)
+Requirement already satisfied: jmespath<1.0.0,>=0.7.1 in /usr/local/lib/python3.7/site-packages (from boto3<1.17.107,~=1.9->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.10.0)
+Collecting s3transfer<0.5.0,>=0.4.0
+  Downloading s3transfer-0.4.2-py2.py3-none-any.whl (79 kB)
+Requirement already satisfied: pycparser in /usr/local/lib/python3.7/site-packages (from cffi->azure-datalake-store<0.1,>=0.0.46->adlfs~=2021.8.1->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2.21)
+Collecting partd>=0.3.10
+  Downloading partd-1.2.0-py3-none-any.whl (19 kB)
+Collecting toolz>=0.8.2
+  Downloading toolz-0.11.2-py3-none-any.whl (55 kB)
+Requirement already satisfied: cloudpickle>=1.1.1 in /usr/local/lib/python3.7/site-packages (from dask~=2021.11.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.6.0)
+Collecting packaging>=20.0
+  Downloading packaging-21.3-py3-none-any.whl (40 kB)
+Collecting ordered-set==4.0.2
+  Downloading ordered-set-4.0.2.tar.gz (10 kB)
+Collecting tornado>=5
+  Downloading tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl (428 kB)
+Collecting msgpack>=0.6.0
+  Downloading msgpack-1.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (299 kB)
+Collecting zict>=0.1.3
+  Downloading zict-2.0.0-py3-none-any.whl (10 kB)
+Requirement already satisfied: setuptools in /usr/local/lib/python3.7/site-packages (from distributed~=2021.11.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (53.0.0)
+Collecting psutil>=5.0
+  Downloading psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (280 kB)
+Collecting jinja2
+  Downloading Jinja2-3.0.3-py3-none-any.whl (133 kB)
+Collecting tblib>=1.6.0
+  Downloading tblib-1.7.0-py2.py3-none-any.whl (12 kB)
+Collecting sortedcontainers!=2.0.0,!=2.0.1
+  Downloading sortedcontainers-2.4.0-py2.py3-none-any.whl (29 kB)
+Collecting starlette==0.14.2
+  Downloading starlette-0.14.2-py3-none-any.whl (60 kB)
+Collecting decorator
+  Downloading decorator-5.1.1-py3-none-any.whl (9.1 kB)
+Collecting google-auth-oauthlib
+  Downloading google_auth_oauthlib-0.4.6-py2.py3-none-any.whl (18 kB)
+Collecting gitdb<5,>=4.0.1
+  Downloading gitdb-4.0.9-py3-none-any.whl (63 kB)
+Collecting smmap<6,>=3.0.1
+  Downloading smmap-5.0.0-py3-none-any.whl (24 kB)
+Collecting pyasn1-modules>=0.2.1
+  Downloading pyasn1_modules-0.2.8-py2.py3-none-any.whl (155 kB)
+Collecting rsa<5,>=3.1.4
+  Downloading rsa-4.8-py3-none-any.whl (39 kB)
+Collecting cachetools<5.0,>=2.0.0
+  Downloading cachetools-4.2.4-py3-none-any.whl (10 kB)
+Collecting jupyter-client
+  Downloading jupyter_client-7.1.2-py3-none-any.whl (130 kB)
+Collecting ipython-genutils
+  Downloading ipython_genutils-0.2.0-py2.py3-none-any.whl (26 kB)
+Collecting traitlets>=4.1.0
+  Downloading traitlets-5.1.1-py3-none-any.whl (102 kB)
+Collecting prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0
+  Downloading prompt_toolkit-3.0.26-py3-none-any.whl (375 kB)
+Collecting jedi>=0.16
+  Downloading jedi-0.18.1-py2.py3-none-any.whl (1.6 MB)
+Collecting matplotlib-inline
+  Downloading matplotlib_inline-0.1.3-py3-none-any.whl (8.2 kB)
+Collecting pickleshare
+  Downloading pickleshare-0.7.5-py2.py3-none-any.whl (6.9 kB)
+Collecting pygments
+  Downloading Pygments-2.11.2-py3-none-any.whl (1.1 MB)
+Collecting backcall
+  Downloading backcall-0.2.0-py2.py3-none-any.whl (11 kB)
+Collecting pexpect>4.3
+  Downloading pexpect-4.8.0-py2.py3-none-any.whl (59 kB)
+Collecting parso<0.9.0,>=0.8.0
+  Downloading parso-0.8.3-py2.py3-none-any.whl (100 kB)
+Collecting absl-py<2,>=0.9
+  Downloading absl_py-1.0.0-py3-none-any.whl (126 kB)
+Collecting google-cloud-storage<2,>=1.20.0
+  Downloading google_cloud_storage-1.44.0-py2.py3-none-any.whl (106 kB)
+Collecting google-api-python-client<2,>=1.7.8
+  Downloading google_api_python_client-1.12.10-py2.py3-none-any.whl (61 kB)
+Collecting requests-toolbelt<1,>=0.8.0
+  Downloading requests_toolbelt-0.9.1-py2.py3-none-any.whl (54 kB)
+Collecting cloudpickle>=1.1.1
+  Downloading cloudpickle-2.0.0-py3-none-any.whl (25 kB)
+Collecting kfp-server-api<2.0.0,>=1.1.2
+  Downloading kfp-server-api-1.7.1.tar.gz (52 kB)
+Collecting jsonschema<4,>=3.0.1
+  Downloading jsonschema-3.2.0-py2.py3-none-any.whl (56 kB)
+Collecting Deprecated<2,>=1.2.7
+  Downloading Deprecated-1.2.13-py2.py3-none-any.whl (9.6 kB)
+Collecting strip-hints<1,>=0.1.8
+  Downloading strip-hints-0.1.10.tar.gz (29 kB)
+Collecting docstring-parser<1,>=0.7.3
+  Downloading docstring_parser-0.13.tar.gz (23 kB)
+  Installing build dependencies: started
+  Installing build dependencies: finished with status 'done'
+  Getting requirements to build wheel: started
+  Getting requirements to build wheel: finished with status 'done'
+    Preparing wheel metadata: started
+    Preparing wheel metadata: finished with status 'done'
+Collecting kfp-pipeline-spec<0.2.0,>=0.1.13
+  Downloading kfp_pipeline_spec-0.1.13-py3-none-any.whl (18 kB)
+Collecting fire<1,>=0.3.1
+  Downloading fire-0.4.0.tar.gz (87 kB)
+Collecting protobuf<4,>=3.13.0
+  Downloading protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)
+Collecting uritemplate<4,>=3.0.1
+  Downloading uritemplate-3.0.1-py2.py3-none-any.whl (15 kB)
+Collecting typer<1.0,>=0.3.2
+  Downloading typer-0.4.0-py3-none-any.whl (27 kB)
+Collecting typing-extensions>=3.7.4
+  Downloading typing_extensions-3.10.0.2-py3-none-any.whl (26 kB)
+Collecting termcolor
+  Downloading termcolor-1.1.0.tar.gz (3.9 kB)
+Collecting httplib2<1dev,>=0.15.0
+  Downloading httplib2-0.20.2-py3-none-any.whl (96 kB)
+Collecting google-api-core<3dev,>=1.21.0
+  Downloading google_api_core-2.4.0-py2.py3-none-any.whl (111 kB)
+Collecting google-auth-httplib2>=0.0.3
+  Downloading google_auth_httplib2-0.1.0-py2.py3-none-any.whl (9.3 kB)
+Collecting googleapis-common-protos<2.0dev,>=1.52.0
+  Downloading googleapis_common_protos-1.54.0-py2.py3-none-any.whl (207 kB)
+Collecting google-cloud-core<3.0dev,>=1.6.0
+  Downloading google_cloud_core-2.2.2-py2.py3-none-any.whl (29 kB)
+Collecting google-resumable-media<3.0dev,>=1.3.0
+  Downloading google_resumable_media-2.1.0-py2.py3-none-any.whl (75 kB)
+Collecting google-crc32c<2.0dev,>=1.0
+  Downloading google_crc32c-1.3.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38 kB)
+Collecting pyparsing!=3.0.0,!=3.0.1,!=3.0.2,!=3.0.3,<4,>=2.4.2
+  Downloading pyparsing-3.0.7-py3-none-any.whl (98 kB)
+Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/site-packages (from jsonschema<4,>=3.0.1->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (4.10.1)
+Collecting pyrsistent>=0.14.0
+  Downloading pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (117 kB)
+Requirement already satisfied: certifi in /usr/local/lib/python3.7/site-packages (from kfp-server-api<2.0.0,>=1.1.2->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2021.10.8)
+Requirement already satisfied: requests-oauthlib in /usr/local/lib/python3.7/site-packages (from kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.3.1)
+Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /usr/local/lib/python3.7/site-packages (from kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.2.3)
+Requirement already satisfied: portalocker<3,>=1.0 in /usr/local/lib/python3.7/site-packages (from msal-extensions~=0.3.0->azure-identity~=1.5->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (1.7.1)
+Requirement already satisfied: isodate>=0.6.0 in /usr/local/lib/python3.7/site-packages (from msrest>=0.6.21->azure-keyvault-secrets~=4.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.6.1)
+Collecting notebook>=5.2.0
+  Downloading notebook-6.4.8-py3-none-any.whl (9.9 MB)
+Collecting nbconvert>=5.4
+  Downloading nbconvert-6.4.1-py3-none-any.whl (557 kB)
+Collecting nbformat>=4.4
+  Downloading nbformat-5.1.3-py3-none-any.whl (178 kB)
+Collecting mistune<2,>=0.8.1
+  Downloading mistune-0.8.4-py2.py3-none-any.whl (16 kB)
+Collecting defusedxml
+  Downloading defusedxml-0.7.1-py2.py3-none-any.whl (25 kB)
+Collecting nbclient<0.6.0,>=0.5.0
+  Downloading nbclient-0.5.10-py3-none-any.whl (69 kB)
+Collecting jupyter-core
+  Downloading jupyter_core-4.9.1-py3-none-any.whl (86 kB)
+Collecting testpath
+  Downloading testpath-0.5.0-py3-none-any.whl (84 kB)
+Collecting pandocfilters>=1.4.1
+  Downloading pandocfilters-1.5.0-py2.py3-none-any.whl (8.7 kB)
+Collecting bleach
+  Downloading bleach-4.1.0-py2.py3-none-any.whl (157 kB)
+Collecting jupyterlab-pygments
+  Downloading jupyterlab_pygments-0.1.2-py2.py3-none-any.whl (4.6 kB)
+Collecting entrypoints>=0.2.2
+  Downloading entrypoints-0.3-py2.py3-none-any.whl (11 kB)
+Collecting MarkupSafe>=2.0
+  Downloading MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (31 kB)
+Collecting pyzmq>=13
+  Downloading pyzmq-22.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.1 MB)
+Collecting terminado>=0.8.3
+  Downloading terminado-0.13.1-py3-none-any.whl (14 kB)
+Collecting argon2-cffi
+  Downloading argon2_cffi-21.3.0-py3-none-any.whl (14 kB)
+Collecting Send2Trash>=1.8.0
+  Downloading Send2Trash-1.8.0-py3-none-any.whl (18 kB)
+Collecting prometheus-client
+  Downloading prometheus_client-0.13.1-py3-none-any.whl (57 kB)
+Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/site-packages (from pandas~=1.2->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (2021.3)
+Collecting locket
+  Downloading locket-0.2.1-py2.py3-none-any.whl (4.1 kB)
+Collecting ptyprocess>=0.5
+  Downloading ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)
+Collecting tenacity>=6.2.0
+  Downloading tenacity-8.0.1-py3-none-any.whl (24 kB)
+Collecting wcwidth
+  Downloading wcwidth-0.2.5-py2.py3-none-any.whl (30 kB)
+Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/site-packages (from pyasn1-modules>=0.2.1->google-auth<2.0dev,>=1.25.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.4.8)
+Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.7/site-packages (from requests~=2.22->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.3)
+Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/site-packages (from requests-oauthlib->kubernetes~=12.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.2.0)
+Collecting greenlet!=0.4.17
+  Downloading greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (150 kB)
+Collecting grpcio-tools<1.42,>1.34.0
+  Downloading grpcio_tools-1.41.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.4 MB)
+Collecting grpcio<1.42,>1.34.0
+  Downloading grpcio-1.41.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.9 MB)
+Requirement already satisfied: wheel in /usr/local/lib/python3.7/site-packages (from strip-hints<1,>=0.1.8->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (0.36.2)
+Collecting ujson>=3.0.0
+  Downloading ujson-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (43 kB)
+Collecting future>=0.18.2
+  Downloading future-0.18.2.tar.gz (829 kB)
+Collecting heapdict
+  Downloading HeapDict-1.0.1-py3-none-any.whl (3.9 kB)
+Collecting argon2-cffi-bindings
+  Downloading argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (86 kB)
+Collecting webencodings
+  Downloading webencodings-0.5.1-py2.py3-none-any.whl (11 kB)
+Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/site-packages (from importlib-metadata->jsonschema<4,>=3.0.1->kfp~=1.8.0->mlrun[complete]@ git+https://github.com/mlrun/mlrun@development) (3.7.0)
+Building wheels for collected packages: adlfs, aiobotocore, ordered-set, kfp, docstring-parser, fire, kfp-server-api, strip-hints, future, mlrun, termcolor
+  Building wheel for adlfs (setup.py): started
+  Building wheel for adlfs (setup.py): finished with status 'done'
+  Created wheel for adlfs: filename=adlfs-2021.8.2-py3-none-any.whl size=21466 sha256=d3076fc23e1d05f49958ba450b9913a80c23c755d3347327d1d2150656fa3185
+  Stored in directory: /root/.cache/pip/wheels/0d/88/1d/e06072abb7fb4d59b5cf94e194e53017dfa2dc47af4dec88b7
+  Building wheel for aiobotocore (setup.py): started
+  Building wheel for aiobotocore (setup.py): finished with status 'done'
+  Created wheel for aiobotocore: filename=aiobotocore-1.4.2-py3-none-any.whl size=49910 sha256=d630bfe25d72229a76e207cb2de8dd29839368673c27edc12229b314660a7e69
+  Stored in directory: /root/.cache/pip/wheels/33/e7/d9/b297a9aa9c43d56bc2463e6e2771655ff638f30b30f0b61fcb
+  Building wheel for ordered-set (setup.py): started
+  Building wheel for ordered-set (setup.py): finished with status 'done'
+  Created wheel for ordered-set: filename=ordered_set-4.0.2-py2.py3-none-any.whl size=8210 sha256=d0a6fcb9c69107866ca516cb90c3a6a1c0dd00c2f55e981d69b476e92fe85c0a
+  Stored in directory: /root/.cache/pip/wheels/73/2b/f6/26e9f84153c25050fe7c09e88f8e32a6be3c7034a38c418319
+  Building wheel for kfp (setup.py): started
+  Building wheel for kfp (setup.py): finished with status 'done'
+  Created wheel for kfp: filename=kfp-1.8.11-py3-none-any.whl size=414450 sha256=62bc86dbc4fbb6d431756182a297f87d5d9f08edfb8c2bab347b81fe0654cad3
+  Stored in directory: /root/.cache/pip/wheels/85/1e/ee/a14b49663bddf9e72d1c269cbe53970167bfabb53cadbbea3a
+  Building wheel for docstring-parser (PEP 517): started
+  Building wheel for docstring-parser (PEP 517): finished with status 'done'
+  Created wheel for docstring-parser: filename=docstring_parser-0.13-py3-none-any.whl size=31866 sha256=6ab7172ddcc24d27d93d31af6438a00053464e76585907654ec90dfb3ecf1886
+  Stored in directory: /root/.cache/pip/wheels/bd/88/3c/d1aa049309f7945178cac9fbe6561a86424f432da57c18ca0f
+  Building wheel for fire (setup.py): started
+  Building wheel for fire (setup.py): finished with status 'done'
+  Created wheel for fire: filename=fire-0.4.0-py2.py3-none-any.whl size=115928 sha256=6704c4bed4908d06fbe2e61f97718607c4ef9dee5a573d09acf9882e6e620757
+  Stored in directory: /root/.cache/pip/wheels/8a/67/fb/2e8a12fa16661b9d5af1f654bd199366799740a85c64981226
+  Building wheel for kfp-server-api (setup.py): started
+  Building wheel for kfp-server-api (setup.py): finished with status 'done'
+  Created wheel for kfp-server-api: filename=kfp_server_api-1.7.1-py3-none-any.whl size=92618 sha256=b156a487ea471572b7c0d0dc85826bb5f6554ce5d097c3d16cdd75e36a93100a
+  Stored in directory: /root/.cache/pip/wheels/68/3f/d5/734c0278dd6c8969cef359edcf059505a61452c5eb0e2760e1
+  Building wheel for strip-hints (setup.py): started
+  Building wheel for strip-hints (setup.py): finished with status 'done'
+  Created wheel for strip-hints: filename=strip_hints-0.1.10-py2.py3-none-any.whl size=22279 sha256=ebdf8455bb18636c3ffe99dc1ffe7262298094e000e46a15df3ef1b809e3770f
+  Stored in directory: /root/.cache/pip/wheels/5e/14/c3/6e44e9b2545f2d570b03f5b6d38c00b7534aa8abb376978363
+  Building wheel for future (setup.py): started
+  Building wheel for future (setup.py): finished with status 'done'
+  Created wheel for future: filename=future-0.18.2-py3-none-any.whl size=491059 sha256=b71c260b8cae9faa06e701eb03743481d68f427d7ed0886bddf8eec6fab17927
+  Stored in directory: /root/.cache/pip/wheels/56/b0/fe/4410d17b32f1f0c3cf54cdfb2bc04d7b4b8f4ae377e2229ba0
+  Building wheel for mlrun (PEP 517): started
+  Building wheel for mlrun (PEP 517): finished with status 'done'
+  Created wheel for mlrun: filename=mlrun-0.0.0+unstable-py3-none-any.whl size=799835 sha256=a37564dbf60ba19531146c0404fa02216cea4492894cbafd9738bb199ce45775
+  Stored in directory: /tmp/pip-ephem-wheel-cache-xz3ex0pi/wheels/cd/42/82/13965317128ea26acc3fb21b24cc254077454998599db6f161
+  Building wheel for termcolor (setup.py): started
+  Building wheel for termcolor (setup.py): finished with status 'done'
+  Created wheel for termcolor: filename=termcolor-1.1.0-py3-none-any.whl size=4829 sha256=09ed568b0b6ea586b107bc3decf5a736c95331052246548b5a560c69a88e9414
+  Stored in directory: /root/.cache/pip/wheels/3f/e3/ec/8a8336ff196023622fbcb36de0c5a5c218cbb24111d1d4c7f2
+Successfully built adlfs aiobotocore ordered-set kfp docstring-parser fire kfp-server-api strip-hints future mlrun termcolor
+Installing collected packages: typing-extensions, traitlets, pyrsistent, attrs, wcwidth, tornado, rsa, pyzmq, pyparsing, pyasn1-modules, ptyprocess, protobuf, parso, nest-asyncio, jupyter-core, jsonschema, ipython-genutils, entrypoints, cachetools, webencodings, pygments, prompt-toolkit, pickleshare, pexpect, packaging, nbformat, matplotlib-inline, MarkupSafe, jupyter-client, jedi, googleapis-common-protos, google-auth, decorator, cryptography, backcall, ujson, toolz, testpath, pandocfilters, nbclient, multidict, mistune, locket, jupyterlab-pygments, jinja2, ipython, httplib2, grpcio, google-crc32c, google-api-core, future, frozenlist, defusedxml, botocore, bleach, argon2-cffi-bindings, yarl, wrapt, v3io, uritemplate, terminado, termcolor, smmap, Send2Trash, s3transfer, pyyaml, prometheus-client, partd, pandas, nbconvert, ipykernel, heapdict, grpcio-tools, greenlet, google-resumable-media, google-cloud-core, google-auth-httplib2, fsspec, cloudpickle, click, asynctest, async-timeout, argon2-cffi, aiosignal, zict, v3iofs, v3io-frames, typer, tblib, tabulate, strip-hints, starlette, sqlalchemy, sortedcontainers, requests-toolbelt, python-editor, pydantic, psutil, ordered-set, notebook, msgpack, msal-extensions, Mako, kubernetes, kfp-server-api, kfp-pipeline-spec, google-cloud-storage, google-api-python-client, gitdb, fire, docstring-parser, Deprecated, dask, boto3, aioitertools, aiohttp, absl-py, tenacity, storey, semver, python-dotenv, pymysql, orjson, nuclio-jupyter, mergedeep, kfp, inflection, humanfriendly, google-auth-oauthlib, GitPython, fastapi, distributed, deepdiff, chardet, azure-storage-blob, azure-identity, azure-datalake-store, alembic, aiobotocore, s3fs, plotly, mlrun, gcsfs, azure-keyvault-secrets, adlfs
+  Attempting uninstall: typing-extensions
+    Found existing installation: typing-extensions 4.0.1
+    Uninstalling typing-extensions-4.0.1:
+      Successfully uninstalled typing-extensions-4.0.1
+  Attempting uninstall: cryptography
+    Found existing installation: cryptography 3.4.8
+    Uninstalling cryptography-3.4.8:
+      Successfully uninstalled cryptography-3.4.8
+  Attempting uninstall: cloudpickle
+    Found existing installation: cloudpickle 1.6.0
+    Uninstalling cloudpickle-1.6.0:
+      Successfully uninstalled cloudpickle-1.6.0
+  Attempting uninstall: msal-extensions
+    Found existing installation: msal-extensions 0.2.2
+    Uninstalling msal-extensions-0.2.2:
+      Successfully uninstalled msal-extensions-0.2.2
+  Attempting uninstall: azure-identity
+    Found existing installation: azure-identity 1.4.1
+    Uninstalling azure-identity-1.4.1:
+      Successfully uninstalled azure-identity-1.4.1
+ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
+azureml-dataprep 2.20.1 requires azure-identity<1.5.0,>=1.2.0, but you have azure-identity 1.7.1 which is incompatible.
+azureml-dataprep 2.20.1 requires cloudpickle<2.0.0,>=1.1.0, but you have cloudpickle 2.0.0 which is incompatible.
+Successfully installed Deprecated-1.2.13 GitPython-3.1.26 Mako-1.1.6 MarkupSafe-2.0.1 Send2Trash-1.8.0 absl-py-1.0.0 adlfs-2021.8.2 aiobotocore-1.4.2 aiohttp-3.8.1 aioitertools-0.8.0 aiosignal-1.2.0 alembic-1.5.8 argon2-cffi-21.3.0 argon2-cffi-bindings-21.2.0 async-timeout-4.0.2 asynctest-0.13.0 attrs-21.4.0 azure-datalake-store-0.0.52 azure-identity-1.7.1 azure-keyvault-secrets-4.3.0 azure-storage-blob-12.9.0 backcall-0.2.0 bleach-4.1.0 boto3-1.17.106 botocore-1.20.106 cachetools-4.2.4 chardet-3.0.4 click-7.1.2 cloudpickle-2.0.0 cryptography-3.3.2 dask-2021.11.2 decorator-5.1.1 deepdiff-5.7.0 defusedxml-0.7.1 distributed-2021.11.2 docstring-parser-0.13 entrypoints-0.3 fastapi-0.67.0 fire-0.4.0 frozenlist-1.3.0 fsspec-2021.8.1 future-0.18.2 gcsfs-2021.8.1 gitdb-4.0.9 google-api-core-2.4.0 google-api-python-client-1.12.10 google-auth-1.35.0 google-auth-httplib2-0.1.0 google-auth-oauthlib-0.4.6 google-cloud-core-2.2.2 google-cloud-storage-1.44.0 google-crc32c-1.3.0 google-resumable-media-2.1.0 googleapis-common-protos-1.54.0 greenlet-1.1.2 grpcio-1.41.1 grpcio-tools-1.41.1 heapdict-1.0.1 httplib2-0.20.2 humanfriendly-8.2 inflection-0.5.1 ipykernel-5.5.6 ipython-7.31.1 ipython-genutils-0.2.0 jedi-0.18.1 jinja2-3.0.3 jsonschema-3.2.0 jupyter-client-7.1.2 jupyter-core-4.9.1 jupyterlab-pygments-0.1.2 kfp-1.8.11 kfp-pipeline-spec-0.1.13 kfp-server-api-1.7.1 kubernetes-12.0.1 locket-0.2.1 matplotlib-inline-0.1.3 mergedeep-1.3.4 mistune-0.8.4 mlrun-0.0.0+unstable msal-extensions-0.3.1 msgpack-1.0.3 multidict-6.0.2 nbclient-0.5.10 nbconvert-6.4.1 nbformat-5.1.3 nest-asyncio-1.5.4 notebook-6.4.8 nuclio-jupyter-0.8.22 ordered-set-4.0.2 orjson-3.3.1 packaging-21.3 pandas-1.3.5 pandocfilters-1.5.0 parso-0.8.3 partd-1.2.0 pexpect-4.8.0 pickleshare-0.7.5 plotly-5.5.0 prometheus-client-0.13.1 prompt-toolkit-3.0.26 protobuf-3.19.4 psutil-5.9.0 ptyprocess-0.7.0 pyasn1-modules-0.2.8 pydantic-1.9.0 pygments-2.11.2 pymysql-1.0.2 pyparsing-3.0.7 pyrsistent-0.18.1 python-dotenv-0.17.1 python-editor-1.0.4 pyyaml-5.4.1 pyzmq-22.3.0 requests-toolbelt-0.9.1 rsa-4.8 s3fs-2021.8.1 s3transfer-0.4.2 semver-2.13.0 smmap-5.0.0 sortedcontainers-2.4.0 sqlalchemy-1.4.31 starlette-0.14.2 storey-0.10.4 strip-hints-0.1.10 tabulate-0.8.9 tblib-1.7.0 tenacity-8.0.1 termcolor-1.1.0 terminado-0.13.1 testpath-0.5.0 toolz-0.11.2 tornado-6.1 traitlets-5.1.1 typer-0.4.0 typing-extensions-3.10.0.2 ujson-5.1.0 uritemplate-3.0.1 v3io-0.5.15 v3io-frames-0.10.2 v3iofs-0.1.10 wcwidth-0.2.5 webencodings-0.5.1 wrapt-1.13.3 yarl-1.7.2 zict-2.0.0
+WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
+WARNING: You are using pip version 21.2.4; however, version 22.0.2 is available.
+You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
+INFO[0128] Taking snapshot of full filesystem...        
+INFO[0148] Pushing image to docker-registry.default-tenant.app.yh41.iguazio-cd1.com/mlrun/func-azureml-yonatan-azureml-utils:latest 
+INFO[0153] Pushed image to 1 destinations               
+> 2022-02-02 18:30:56,789 [info] starting run azureml-utils-train uid=48dbbe26a2a34b5baaec5ca8aba3de5e DB=http://mlrun-api:8080
+> 2022-02-02 18:30:56,988 [info] Job is running in the background, pod: azureml-utils-train-7pp86
+> 2022-02-02 18:31:30,311 [warning] Failed resolving version info. Ignoring and using defaults
+> 2022-02-02 18:31:32,893 [warning] Server or client version is unstable. Assuming compatible: {'server_version': '0.0.0+unstable', 'client_version': '0.0.0+unstable'}
+Failure while loading azureml_run_type_providers. Failed to load entrypoint automl = azureml.train.automl.run:AutoMLRun._from_run_dto with exception (cloudpickle 2.0.0 (/usr/local/lib/python3.7/site-packages), Requirement.parse('cloudpickle<2.0.0,>=1.1.0'), {'azureml-dataprep'}).
+> 2022-02-02 18:31:34,680 [info] Loading AzureML Workspace
+> 2022-02-02 18:31:36,956 [info] Initializing AzureML experiment azure-automl-test
+> 2022-02-02 18:31:40,005 [info] Initializing AzureML compute target azureml-cpu
+> 2022-02-02 18:31:40,206 [info] Found existing cluster, will use it.
+Succeeded
+AmlCompute wait for completion finished
+
+Minimum number of nodes requested have been provisioned
+> 2022-02-02 18:31:40,322 [info] Connecting to AzureML experiment default datastore
+> 2022-02-02 18:31:41,624 [info] Retrieving feature vector and uploading to Azure blob storage: az://azureml-blobstore-27f8977b-4946-4ca0-bdc5-5a685d2fe8d7/iris.csv
+> 2022-02-02 18:31:41,912 [info] Registering dataset iris in Azure ML
+> 2022-02-02 18:31:41,912 [info] OpenSSL version must be 1.1. Overriding the OpenSSL version to 1.1
+> 2022-02-02 18:31:49,558 [info] Setting up experiment parameters
+> 2022-02-02 18:31:49,812 [info] Submitting and running experiment
+Submitting remote run.
+Parent Run ID: AutoML_35c51a81-98fd-44fb-aa23-3192c3aca08d
+https://ml.azure.com/runs/AutoML_35c51a81-98fd-44fb-aa23-3192c3aca08d?wsid=/subscriptions/8d81bc0b-6abd-4395-be83-000251d9fdbe/resourcegroups/nick/workspaces/NickAzureML&tid=af053911-a8b7-450d-9f58-0c08567d4769
+
+Current status: ModelSelection. Beginning model selection.
+
+****************************************************************************************************
+DATA GUARDRAILS: 
+
+TYPE:         Class balancing detection
+STATUS:       PASSED
+DESCRIPTION:  Your inputs were analyzed, and all classes are balanced in your training data.
+              Learn more about imbalanced data: https://aka.ms/AutomatedMLImbalancedData
+
+****************************************************************************************************
+
+****************************************************************************************************
+ITERATION: The iteration being evaluated.
+PIPELINE: A summary description of the pipeline being evaluated.
+DURATION: Time taken for the current iteration.
+METRIC: The result of computing score on the fitted pipeline.
+BEST: The best observed score thus far.
+****************************************************************************************************
+
+ ITERATION   PIPELINE                                       DURATION      METRIC      BEST
+         0   RobustScaler LogisticRegression                0:00:21       0.9667    0.9667
+         1   StandardScalerWrapper SVM                      0:00:18       0.9533    0.9667
+         2   StandardScalerWrapper LogisticRegression       0:00:22       0.9733    0.9733
+         3   MaxAbsScaler LogisticRegression                0:00:22       0.9533    0.9733
+         4   MaxAbsScaler LogisticRegression                0:00:21       0.9733    0.9733
+
+********************************************************************************************
+
+> 2022-02-02 18:38:28,144 [info] Registering model
+> 2022-02-02 18:38:29,495 [info] Registered model with name 'iris-model', id 'iris-model:178', version '178'
+> 2022-02-02 18:38:29,495 [info] Downloading model iris-model:178
+> 2022-02-02 18:38:34,083 [info] Logging model_1_standardscaler_logisticregression model to MLRun
+> 2022-02-02 18:38:34,621 [info] Registering model
+> 2022-02-02 18:38:35,519 [info] Registered model with name 'iris-model', id 'iris-model:179', version '179'
+> 2022-02-02 18:38:35,519 [info] Downloading model iris-model:179
+> 2022-02-02 18:38:39,972 [info] Logging model_2_maxabsscaler_logisticregression model to MLRun
+> 2022-02-02 18:38:40,317 [info] Registering model
+> 2022-02-02 18:38:41,087 [info] Registered model with name 'iris-model', id 'iris-model:180', version '180'
+> 2022-02-02 18:38:41,087 [info] Downloading model iris-model:180
+> 2022-02-02 18:38:46,299 [info] Logging model_3_robustscaler_logisticregression model to MLRun
+> 2022-02-02 18:38:47,615 [info] run executed, status=completed
+final state: completed
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
azureml-yonatan0Feb 02 18:31:32completedazureml-utils-train
v3io_user=yonatan
kind=job
owner=yonatan
host=azureml-utils-train-7pp86
dataset
experiment_name=azure-automl-test
cpu_cluster_name=azureml-cpu
dataset_name=iris
dataset_description=iris training data
label_column_name=label
create_new_version=True
register_model_name=iris-model
save_n_models=3
automl_settings={'task': 'classification', 'debug_log': 'automl_errors.log', 'enable_early_stopping': False, 'allowed_models': ['LogisticRegression', 'SGD', 'SVM'], 'iterations': 5, 'iteration_timeout_minutes': 2, 'max_concurrent_iterations': 2, 'max_cores_per_iteration': -1, 'n_cross_validations': 5, 'primary_metric': 'accuracy', 'featurization': 'off', 'model_explainability': False, 'enable_voting_ensemble': False, 'enable_stack_ensemble': False}
dataset_blob_path=az://azureml-blobstore-27f8977b-4946-4ca0-bdc5-5a685d2fe8d7/iris.csv
best_iteration=1
auc_macro=0.9973298059964726
auc_micro=0.9979999999999999
norm_macro_recall=0.9594444444444443
balanced_accuracy=0.9729629629629629
f1_score_macro=0.9721779225097302
weighted_accuracy=0.9739694654594448
average_precision_score_weighted=0.9953861693861693
f1_score_weighted=0.9730901151988108
precision_score_micro=0.9733333333333334
matthews_correlation=0.9613232982405628
recall_score_macro=0.9729629629629629
precision_score_weighted=0.9767380952380952
recall_score_micro=0.9733333333333334
precision_score_macro=0.9754761904761905
average_precision_score_macro=0.9954059829059829
accuracy=0.9733333333333334
auc_weighted=0.9972857142857142
recall_score_weighted=0.9733333333333334
f1_score_micro=0.9733333333333334
average_precision_score_micro=0.9962096994520057
log_loss=0.07548089806904337
model
iteration_results
parallel_coordinates
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2022-02-02 18:38:48,608 [info] run executed, status=completed
+
+
+
+
+

View the run result: (more details in the UI)

+
+
+
azureml_run.artifact('iteration_results').show()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
stateiterparam.data_trans_class_nameparam.data_trans_moduleparam.data_trans_spec_classparam.train_class_nameparam.train_moduleparam.train_param_kwargs_Cparam.train_param_kwargs_class_weightparam.train_spec_class...output.precision_score_weightedoutput.recall_score_microoutput.precision_score_macrooutput.average_precision_score_macrooutput.accuracyoutput.auc_weightedoutput.recall_score_weightedoutput.f1_score_microoutput.average_precision_score_microoutput.log_loss
0completed1StandardScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model16.768329NaNsklearn...0.9767380.9733330.9754760.9954060.9733330.9972860.9733330.9733330.9962100.075481
1completed2MaxAbsScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model719.685673NaNsklearn...0.9767380.9733330.9754760.9952270.9733330.9972860.9733330.9733330.9962110.072493
2completed3RobustScalersklearn.preprocessingpreprocLogisticRegressionsklearn.linear_model1048.113134balancedsklearn...0.9683590.9666670.9664100.9942170.9666670.9965980.9666670.9666670.9955950.086160
+

3 rows × 31 columns

+
+
+
+
+
+

4. Deploy Model Serving#

+
+
+
# Importing serving function from marketplace:
+serving_fn = mlrun.new_function("serving", kind="serving", image="yhaviv/mlrun:dev")
+serving_fn.with_code(body=" ")
+serving_fn.with_requirements("./requirements.txt")
+
+# Set the real-time pipeline topology
+serving_fn.set_topology(
+    'router',
+    'mlrun.serving.routers.VotingEnsemble'
+)
+
+# Add the trained models:
+artifacts = mlrun.get_run_db().list_artifacts(project=project.name)
+models = {f"{model['algorithm']}{i}" :f"{model['db_key']}#{model['iter']}"
+          for i, model in enumerate(artifacts) if model["kind"]=="model"}
+
+for name, path in models.items():
+    serving_fn.add_model(
+        name,
+        class_name="mlrun.frameworks.sklearn.PickleModelServer",
+        model_path=project.get_artifact_uri(path))
+
+serving_fn.spec.graph.plot()
+
+
+
+
+_images/c0f81135bd698ac922e9ba61cadf04c6e39974dbd5ac9a1cf873894bf193d023.svg +
+
+

Building and Deploying the Serving Function

+
+
+
function_address = serving_fn.deploy()
+
+
+
+
+
> 2022-02-02 18:38:48,785 [info] Starting remote function deploy
+
+
+
+
+
+
+

5. Using the Live Model-Serving Function#

+
+
+
print (f'The address for the function is {function_address} \n')
+
+!curl $function_address
+
+
+
+
+
+
+
# Data for testing:
+source_df = mlrun.get_dataitem(DATA_URL).as_df()
+test_vector = source_df.sample(5).drop('label', axis=1).values.tolist()
+test_vector
+
+
+
+
+

After deploying the serving function with the required model we can make prediction:

+
+
+
serving_fn.invoke(f'/v2/models/infer', {"inputs": test_vector})
+
+
+
+
+
+
+

6. Clean up#

+

For cleaning up AzureML resources see: +https://docs.microsoft.com/en-us/azure/machine-learning/tutorial-auto-train-models#clean-up-resources

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/static/function.html b/functions/master/azureml_utils/1.4.0/static/function.html new file mode 100644 index 00000000..3aaa82f5 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/function.html @@ -0,0 +1,282 @@ + + + + + + + + + + + Source + + + + +
+        
+verbose: false
+spec:
+  command: ''
+  build:
+    auto_build: true
+    code_origin: ''
+    with_mlrun: true
+    requirements:
+    - azureml-core==1.54.0.post1
+    - azureml-train-automl-client==1.54.0.post1
+    - plotly~=5.4
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK
+    commands:
+    - apt-get update && apt-get install -y --no-install-recommends git
+    - apt install -y liblttng-ust0
+    base_image: python:3.9-bullseye
+    origin_filename: ''
+  default_handler: train
+  allow_empty_resources: true
+  disable_auto_mount: false
+  image: ''
+  entry_points:
+    init_compute:
+      doc: 'Initialize Azure ML compute target to run experiment. Checks for
+
+        existing compute target and creates new if does not exist.'
+      name: init_compute
+      lineno: 102
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: cpu_cluster_name
+        type: str
+        doc: Name of Azure ML compute target. Created if does not exist.
+      - name: vm_size
+        type: str
+        doc: Azure machine type for compute target.
+        default: STANDARD_D2_V2
+      - name: max_nodes
+        type: int
+        doc: Maximum number of concurrent compute targets.
+        default: 1
+      outputs:
+      - doc: Azure ML Compute Target.
+        type: ComputeTarget
+      has_varargs: false
+    register_dataset:
+      doc: 'Register dataset object (can be also an Iguazio FeatureVector) in Azure
+        ML.
+
+        Uploads parquet file to Azure blob storage and registers
+
+        that file as a dataset in Azure ML.'
+      name: register_dataset
+      lineno: 138
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: dataset_name
+        type: str
+        doc: Name of Azure dataset to register.
+      - name: dataset_description
+        type: str
+        doc: Description of Azure dataset to register.
+      - name: data
+        type: DataItem
+        doc: MLRun FeatureVector or dataset object to upload.
+      - name: create_new_version
+        type: bool
+        doc: Register Azure dataset as new version. Must be used when modifying dataset
+          schema.
+        default: false
+      has_varargs: false
+    download_model:
+      doc: Download trained model from Azure ML to local filesystem.
+      name: download_model
+      lineno: 217
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: model_name
+        type: str
+        doc: Name of trained and registered model.
+      - name: model_version
+        type: int
+        doc: Version of model to download.
+      - name: target_dir
+        type: str
+        doc: Target directory to download model.
+        default: .
+      outputs:
+      - type: None
+      has_varargs: false
+    upload_model:
+      doc: Upload pre-trained model from local filesystem to Azure ML.
+      name: upload_model
+      lineno: 238
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: model_name
+        type: str
+        doc: Name of trained and registered model.
+      - name: model_path
+        type: str
+        doc: Path to file on local filesystem.
+      - name: model_description
+        type: str
+        doc: Description of models.
+        default: null
+      - name: model_tags
+        type: dict
+        doc: KV pairs of model tags.
+        default: null
+      outputs:
+      - type: None
+      has_varargs: false
+    submit_training_job:
+      doc: 'Submit training job to Azure AutoML and download trained model
+
+        when completed. Uses previously registered dataset for training.'
+      name: submit_training_job
+      lineno: 352
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: experiment
+        type: Experiment
+        doc: Azure experiment.
+      - name: compute_target
+        type: ComputeTarget
+        doc: Azure compute target.
+      - name: register_model_name
+        type: str
+        doc: Name of model to register in Azure.
+      - name: registered_dataset_name
+        type: str
+        doc: Name of dataset registered in Azure ML.
+      - name: automl_settings
+        type: dict
+        doc: JSON string of all Azure AutoML settings.
+      - name: training_set
+        type: DataItem
+        doc: Training set to log with model. For model monitoring integration.
+      - name: label_column_name
+        type: str
+        doc: Name of target column in dataset.
+        default: ''
+      - name: save_n_models
+        type: int
+        doc: How many of the top performing models to log.
+        default: 3
+      - name: show_output
+        type: bool
+        doc: Displaying Azure logs.
+        default: true
+      outputs:
+      - type: None
+      has_varargs: false
+    train:
+      doc: 'Whole training flow for Azure AutoML. Registers dataset/feature vector,
+
+        submits training job to Azure AutoML, and downloads trained model
+
+        when completed.'
+      name: train
+      lineno: 469
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: dataset
+        type: DataItem
+        doc: MLRun FeatureVector or dataset URI to upload. Will drop index before
+          uploading when it is a FeatureVector.
+      - name: experiment_name
+        type: str
+        doc: Name of experiment to create in Azure ML.
+        default: ''
+      - name: cpu_cluster_name
+        type: str
+        doc: Name of Azure ML compute target. Created if does not exist.
+        default: ''
+      - name: vm_size
+        type: str
+        doc: Azure machine type for compute target.
+        default: STANDARD_D2_V2
+      - name: max_nodes
+        type: int
+        doc: Maximum number of concurrent compute targets.
+        default: 1
+      - name: dataset_name
+        type: str
+        doc: Name of Azure dataset to register.
+        default: ''
+      - name: dataset_description
+        type: str
+        doc: Description of Azure dataset to register.
+        default: ''
+      - name: create_new_version
+        type: bool
+        doc: Register Azure dataset as new version. Must be used when modifying dataset
+          schema.
+        default: false
+      - name: label_column_name
+        type: str
+        doc: Target column in dataset.
+        default: ''
+      - name: register_model_name
+        type: str
+        doc: Name of model to register in Azure.
+        default: ''
+      - name: save_n_models
+        type: int
+        doc: How many of the top performing models to log.
+        default: 1
+      - name: log_azure
+        type: bool
+        doc: Displaying Azure logs.
+        default: true
+      - name: automl_settings
+        type: str
+        doc: JSON string of all Azure AutoML settings.
+        default: null
+      outputs:
+      - type: None
+      has_varargs: false
+  description: Azure AutoML integration in MLRun, including utils functions for training
+    models on Azure AutoML platfrom.
+kind: job
+metadata:
+  categories:
+  - model-serving
+  - utils
+  tag: ''
+  name: azureml-utils
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/static/item.html b/functions/master/azureml_utils/1.4.0/static/item.html new file mode 100644 index 00000000..60bd9b2e --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/item.html @@ -0,0 +1,73 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+- model-serving
+- utils
+description: Azure AutoML integration in MLRun, including utils functions for training
+  models on Azure AutoML platfrom.
+doc: ''
+example: azureml_utils.ipynb
+generationDate: 2022-08-28:17-25
+hidden: false
+icon: ''
+labels:
+  author: yonish
+maintainers: []
+marketplaceType: ''
+mlrunVersion: 1.7.0
+name: azureml_utils
+platformVersion: 3.5.3
+spec:
+  extra_spec:
+    allow_empty_resources: true
+    build:
+      auto_build: true
+      commands:
+      - apt-get update && apt-get install -y --no-install-recommends git
+      - apt install -y liblttng-ust0
+      with_mlrun: true
+  filename: azureml_utils.py
+  handler: train
+  image: python:3.9-bullseye
+  kind: job
+  requirements:
+  - azureml-core==1.54.0.post1
+  - azureml-train-automl-client==1.54.0.post1
+  - plotly~=5.4
+url: ''
+version: 1.4.0
+test_valid: True
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/1.4.0/static/source.html b/functions/master/azureml_utils/1.4.0/static/source.html new file mode 100644 index 00000000..9c61cff8 --- /dev/null +++ b/functions/master/azureml_utils/1.4.0/static/source.html @@ -0,0 +1,603 @@ + + + + + + + + + + + Source + + + + +
+        
+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+import json
+import logging
+from typing import Tuple, List
+
+from mlrun import MLClientCtx, DataItem, get_dataitem
+import mlrun.feature_store as f_store
+import mlrun.datastore
+import mlrun.utils
+from mlrun.datastore.targets import ParquetTarget
+
+from azureml.core.authentication import ServicePrincipalAuthentication
+from azureml.core.workspace import Workspace
+from azureml.core.experiment import Experiment
+from azureml.core.dataset import Dataset
+from azureml.core.model import Model
+from azureml.core.compute import ComputeTarget, AmlCompute
+from azureml.core.compute_target import ComputeTargetException
+from azureml.core.script_run import ScriptRun
+
+from azureml.train.automl import AutoMLConfig
+from azureml.train.automl.run import AutoMLRun
+
+
+def _env_or_secret(context, key):
+    if key in os.environ:
+        return os.environ[key]
+    return context.get_secret(key)
+
+
+def _load_workspace(context: MLClientCtx) -> Workspace:
+    """
+    Loading AzureML Workspace with Azure secrets.
+
+    :param context: MLRun context.
+    :returns:       AzureML Workspace
+    """
+
+    if hasattr(context, "_azure_workspace"):
+        return context._azure_workspace
+
+    context.logger.info("Loading AzureML Workspace")
+    # Azure service authentication:
+    service_authentication = ServicePrincipalAuthentication(
+        tenant_id=_env_or_secret(context, "AZURE_TENANT_ID"),
+        service_principal_id=_env_or_secret(context, "AZURE_SERVICE_PRINCIPAL_ID"),
+        service_principal_password=_env_or_secret(
+            context, "AZURE_SERVICE_PRINCIPAL_PASSWORD"
+        ),
+    )
+
+    # Loading Azure workspace:
+    workspace = Workspace(
+        subscription_id=_env_or_secret(context, "AZURE_SUBSCRIPTION_ID"),
+        resource_group=_env_or_secret(context, "AZURE_RESOURCE_GROUP"),
+        workspace_name=_env_or_secret(context, "AZURE_WORKSPACE_NAME"),
+        auth=service_authentication,
+    )
+
+    context._azure_workspace = workspace
+    return workspace
+
+
+def _init_experiment(
+    context: MLClientCtx, experiment_name: str
+) -> Tuple[Workspace, Experiment]:
+    """
+    Initialize workspace and experiment in Azure ML. Uses Service
+    Principal authentication via environment variables.
+
+    :param context:         MLRun context.
+    :param experiment_name: Name of experiment to create in Azure ML.
+    :returns:               Azure ML Workspace and Experiment.
+    """
+
+    # Initialize experiment via Service Principal Authentication:
+    # https://docs.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication#use-service-principal-authentication
+
+    workspace = _load_workspace(context)
+
+    context.logger.info(f"Initializing AzureML experiment {experiment_name}")
+    # Creating experiment:
+    experiment = Experiment(workspace, experiment_name)
+
+    return workspace, experiment
+
+
+def init_compute(
+    context: MLClientCtx,
+    cpu_cluster_name: str,
+    vm_size: str = "STANDARD_D2_V2",
+    max_nodes: int = 1,
+) -> ComputeTarget:
+    """
+    Initialize Azure ML compute target to run experiment. Checks for
+    existing compute target and creates new if does not exist.
+
+    :param context:          MLRun context.
+    :param cpu_cluster_name: Name of Azure ML compute target. Created if does not exist.
+    :param vm_size:          Azure machine type for compute target.
+    :param max_nodes:        Maximum number of concurrent compute targets.
+    :returns:                Azure ML Compute Target.
+    """
+
+    workspace = _load_workspace(context)
+    context.logger.info(f"Initializing AzureML compute target {cpu_cluster_name}")
+
+    # Verify that cluster does not exist already:
+    try:
+        compute_target = ComputeTarget(workspace=workspace, name=cpu_cluster_name)
+        context.logger.info("Found existing cluster, will use it.")
+    except ComputeTargetException:
+        compute_config = AmlCompute.provisioning_configuration(
+            vm_size=vm_size, max_nodes=max_nodes
+        )
+        compute_target = ComputeTarget.create(
+            workspace, cpu_cluster_name, compute_config
+        )
+
+    compute_target.wait_for_completion(show_output=True)
+    return compute_target
+
+
+def register_dataset(
+    context: MLClientCtx,
+    dataset_name: str,
+    dataset_description: str,
+    data: DataItem,
+    create_new_version: bool = False,
+):
+    """
+    Register dataset object (can be also an Iguazio FeatureVector) in Azure ML.
+    Uploads parquet file to Azure blob storage and registers
+    that file as a dataset in Azure ML.
+
+    :param context:               MLRun context.
+    :param dataset_name:          Name of Azure dataset to register.
+    :param dataset_description:   Description of Azure dataset to register.
+    :param data:                  MLRun FeatureVector or dataset object to upload.
+    :param create_new_version:    Register Azure dataset as new version. Must be used when
+                                  modifying dataset schema.
+    """
+
+    # test for Azure storage connection environment variable or secret:
+    assert _env_or_secret(
+        context, "AZURE_STORAGE_CONNECTION_STRING"
+    ), "AZURE_STORAGE_CONNECTION_STRING secret not set"
+
+    # Connect to AzureML experiment and datastore:
+    context.logger.info("Connecting to AzureML experiment default datastore")
+
+    workspace = _load_workspace(context)
+    datastore = workspace.get_default_datastore()
+
+    # Azure blob path (default datastore for workspace):
+    blob_path = f"az://{datastore.container_name}/{dataset_name}"
+
+    store_uri_prefix, _ = mlrun.datastore.parse_store_uri(data.artifact_url)
+    feature_vector_case = mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix
+    # Retrieve data source as dataframe:
+    if feature_vector_case:
+        # FeatureVector case:
+        context.logger.info(
+            f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}"
+        )
+        f_store.get_offline_features(data.meta.uri, target=ParquetTarget(path=blob_path))
+    else:
+        blob_path += data.suffix
+        # DataItem case:
+        context.logger.info(
+            f"Retrieving feature vector and uploading to Azure blob storage: {blob_path}"
+        )
+        data_in_bytes = data.get()
+        get_dataitem(blob_path).put(data_in_bytes)
+
+    # Register dataset in AzureML:
+    context.logger.info(f"Registering dataset {dataset_name} in Azure ML")
+    if data.suffix == ".parquet" or feature_vector_case:
+        dataset = Dataset.Tabular.from_parquet_files(
+            path=(datastore, f"{dataset_name}.parquet"), validate=False
+        )
+    else:
+        context.logger.info(
+            f"OpenSSL version must be 1.1. Overriding the OpenSSL version to 1.1"
+        )
+        # OpenSSL version must be 1.1
+        os.environ["CLR_OPENSSL_VERSION_OVERRIDE"] = "1.1"
+        dataset = Dataset.Tabular.from_delimited_files(
+            path=(datastore, f"{dataset_name}{data.suffix}"), validate=False
+        )
+
+    dataset.register(
+        workspace=workspace,
+        name=dataset_name,
+        description=dataset_description,
+        create_new_version=create_new_version,
+    )
+
+    # Output registered dataset name in Azure:
+    context.log_result("dataset_blob_path", blob_path)
+
+
+def download_model(
+    context: MLClientCtx,
+    model_name: str,
+    model_version: int,
+    target_dir: str = ".",
+) -> None:
+    """
+    Download trained model from Azure ML to local filesystem.
+
+    :param context:       MLRun context.
+    :param model_name:    Name of trained and registered model.
+    :param model_version: Version of model to download.
+    :param target_dir:    Target directory to download model.
+    """
+    # Loading workspace if not provided:
+    workspace = _load_workspace(context)
+    context.logger.info(f"Downloading model {model_name}:{model_version}")
+    model = Model(workspace, model_name, version=model_version)
+    model.download(target_dir=target_dir, exist_ok=True)
+
+
+def upload_model(
+    context: MLClientCtx,
+    model_name: str,
+    model_path: str,
+    model_description: str = None,
+    model_tags: dict = None,
+) -> None:
+    """
+    Upload pre-trained model from local filesystem to Azure ML.
+    :param context:           MLRun context.
+    :param model_name:        Name of trained and registered model.
+    :param model_path:        Path to file on local filesystem.
+    :param model_description: Description of models.
+    :param model_tags:        KV pairs of model tags.
+    """
+    # Loading workspace if not provided:
+    workspace = _load_workspace(context)
+
+    context.logger.info(f"Upload model {model_name} from {model_path}")
+    Model.register(
+        workspace=workspace,
+        model_path=model_path,
+        model_name=model_name,
+        description=model_description,
+        tags=model_tags,
+    )
+
+
+def _get_top_n_runs(
+    remote_run: AutoMLRun, n: int = 5, primary_metric: str = "accuracy"
+) -> List[ScriptRun]:
+    """
+    Get top N complete runs from experiment sorted by primary metric.
+
+    :param remote_run:     Azure ML Run.
+    :param n:              Number of top runs to return.
+    :param primary_metric: Metric to sort by.
+
+    :returns:              List of top N runs sorted by primary metric.
+    """
+    # Collect all models:
+    complete_runs = [
+        run
+        for run in remote_run.get_children(status="Completed")
+        if not any(s in run.id for s in ["setup", "worker"])
+    ]
+
+    # Checking that the required number of runs are done:
+    if len(complete_runs) < n:
+        raise ValueError(f"Expected {n} runs but only received {len(complete_runs)}")
+
+    # Sorting by the primary metric:
+    sorted_runs = sorted(
+        complete_runs, key=lambda run: run.get_metrics()[primary_metric], reverse=True
+    )
+    return sorted_runs[:n]
+
+
+def _get_model_hp(
+    run: ScriptRun,
+) -> dict:
+    """
+    Get hyper-parameters of trained AzureML model.
+    Combine the hyper-parameters of the data transformation and training to a dictionary.
+    The prefix of the dictionary keys corresponds to 'data transformation' and 'training'.
+
+    :param run: Run object of AzureML trained model.
+
+    :returns:    A dictionary as described in the docstring.
+    """
+
+    spec_field = "pipeline_spec"
+    if spec_field not in run.properties:
+        return {}
+    spec_string = run.properties[spec_field]
+    spec_dict = json.loads(spec_string)
+
+    if "objects" not in spec_dict:
+        # No hyper-params
+        return {}
+    hp_dicts = spec_dict["objects"]
+    # after training there are two hyper-parameters dicts inside the run object:
+    assert (
+        len(hp_dicts) == 2
+    ), "after training there are two hyper-parameters dicts inside the run object"
+    result_dict = {}
+    dict_keys = [
+        ["data_trans_class_name", "data_trans_module", "data_trans_spec_class"],
+        [
+            "train_class_name",
+            "train_module",
+            "train_param_kwargs_C",
+            "train_param_kwargs_class_weight",
+            "train_spec_class",
+        ],
+    ]
+
+    # creating hyper-params dict with key prefixes for each part:
+    kwargs_prefix = "param_kwargs"
+    for d, name, keys in zip(hp_dicts, ["data_trans", "train"], dict_keys):
+        for key in keys:
+
+            if kwargs_prefix in key:
+                result_dict[key] = d[kwargs_prefix][
+                    key.replace(f"{name}_{kwargs_prefix}_", "")
+                ]
+            else:
+                result_dict[key] = d[key.replace(f"{name}_", "")]
+            if not result_dict[key]:
+                result_dict[key] = ""
+
+    return result_dict
+
+
+def submit_training_job(
+    context: MLClientCtx,
+    experiment: Experiment,
+    compute_target: ComputeTarget,
+    register_model_name: str,
+    registered_dataset_name: str,
+    automl_settings: dict,
+    training_set: DataItem,
+    label_column_name: str = '',
+    save_n_models: int = 3,
+    show_output: bool = True,
+) -> None:
+    """
+    Submit training job to Azure AutoML and download trained model
+    when completed. Uses previously registered dataset for training.
+
+    :param context:                 MLRun context.
+    :param experiment:              Azure experiment.
+    :param compute_target:          Azure compute target.
+    :param register_model_name:     Name of model to register in Azure.
+    :param registered_dataset_name: Name of dataset registered in Azure ML.
+    :param label_column_name:       Name of target column in dataset.
+    :param automl_settings:         JSON string of all Azure AutoML settings.
+    :param training_set:            Training set to log with model. For model
+                                    monitoring integration.
+    :param show_output:             Displaying Azure logs.
+    :param save_n_models:           How many of the top performing models to log.
+    """
+    # Loading workspace if not provided:
+    workspace = _load_workspace(context)
+
+    # Setup experiment:
+    context.logger.info("Setting up experiment parameters")
+    dataset = Dataset.get_by_name(workspace, name=registered_dataset_name)
+
+    # Get training set to log with model:
+    feature_vector = None
+    store_uri_prefix, _ = mlrun.datastore.parse_store_uri(training_set.artifact_url)
+    if mlrun.utils.StorePrefix.FeatureVector == store_uri_prefix:
+        feature_vector = training_set.meta.uri
+        label_column_name = label_column_name or training_set.meta.status.label_column
+        context.logger.info(f'label column name: {label_column_name}')
+        training_set = f_store.get_offline_features(feature_vector).to_dataframe()
+    else:
+        training_set = training_set.as_df()
+
+    automl_config = AutoMLConfig(
+        compute_target=compute_target,
+        training_data=dataset,
+        verbosity=logging.INFO,
+        label_column_name=label_column_name,
+        **automl_settings,
+    )
+
+    # Run experiment on AzureML:
+    context.logger.info("Submitting and running experiment")
+    remote_run = experiment.submit(automl_config)
+    remote_run.wait_for_completion(show_output=show_output)
+    if show_output:
+        # Azure log ending row:
+        print(f"\n{'*' * 92}\n")
+    # Get top N runs to log:
+    top_runs = _get_top_n_runs(
+        remote_run=remote_run,
+        n=save_n_models,
+        primary_metric=automl_settings["primary_metric"],
+    )
+
+    # Register, download, and log models:
+    for i, run in enumerate(top_runs):
+        # Register model:
+        context.logger.info("Registering model")
+        model = run.register_model(
+            model_name=register_model_name, model_path="outputs/model.pkl"
+        )
+        context.logger.info(
+            f"Registered model with name '{model.name}', id '{model.id}', version '{model.version}'"
+        )
+
+        # Download model locally:
+        download_model(
+            context=context,
+            model_name=register_model_name,
+            model_version=model.version,
+            target_dir=f"./{model.version}",
+        )
+
+        metrics = {k.lower(): val for k, val in run.get_metrics().items()}
+        del metrics["confusion_matrix"]
+        del metrics["accuracy_table"]
+
+        # Collect model hyper-parameters:
+        model_hp_dict = _get_model_hp(run)
+        with context.get_child_context(**model_hp_dict) as child:
+            model_key = f"model_{i + 1}_{model_hp_dict['data_trans_class_name'].lower()}_{model_hp_dict['train_class_name'].lower()}"
+            # Log model:
+            context.logger.info(
+                f"Logging {model_key} model to MLRun"
+            )
+            child.log_results(metrics)
+            child.log_model(
+                "model",
+                db_key=model_key,
+                artifact_path=context.artifact_subpath("models"),
+                metrics=metrics,
+                model_file=f"{model.version}/model.pkl",
+                training_set=training_set,
+                label_column=label_column_name,
+                feature_vector=feature_vector,
+                framework="AzureML",
+                algorithm=model_hp_dict.get("train_class_name"),
+            )
+            if i == 0:
+                # This also logs the model:
+                child.mark_as_best()
+
+
+def train(
+    # MlRun
+    context: MLClientCtx,
+    dataset: DataItem,
+    # Init experiment and compute
+    experiment_name: str = "",
+    cpu_cluster_name: str = "",
+    vm_size: str = "STANDARD_D2_V2",
+    max_nodes: int = 1,
+    # Register dataset
+    dataset_name: str = "",
+    dataset_description: str = "",
+    create_new_version: bool = False,
+    label_column_name: str = "",
+    # Submit training job
+    register_model_name: str = "",
+    save_n_models: int = 1,
+    log_azure: bool = True,
+    automl_settings: str = None,
+) -> None:
+    """
+    Whole training flow for Azure AutoML. Registers dataset/feature vector,
+    submits training job to Azure AutoML, and downloads trained model
+    when completed.
+
+    :param context:             MLRun context.
+
+    :param dataset:             MLRun FeatureVector or dataset URI to upload. Will drop
+                                index before uploading when it is a FeatureVector.
+
+    :param experiment_name:     Name of experiment to create in Azure ML.
+    :param cpu_cluster_name:    Name of Azure ML compute target. Created if does not exist.
+    :param vm_size:             Azure machine type for compute target.
+    :param max_nodes:           Maximum number of concurrent compute targets.
+
+    :param dataset_name:        Name of Azure dataset to register.
+    :param dataset_description: Description of Azure dataset to register.
+
+    :param create_new_version:  Register Azure dataset as new version. Must be used when
+                                modifying dataset schema.
+    :param label_column_name:   Target column in dataset.
+
+    :param register_model_name: Name of model to register in Azure.
+    :param save_n_models:       How many of the top performing models to log.
+    :param log_azure:           Displaying Azure logs.
+    :param automl_settings:     JSON string of all Azure AutoML settings.
+    """
+    if not automl_settings:
+        automl_settings = {
+            "task": "classification",
+            "debug_log": "automl_errors.log",
+            # "experiment_exit_score": 0.9,
+            "enable_early_stopping": False,
+            "allowed_models": ["LogisticRegression", "SGD", "SVM"],
+            "iterations": 3,
+            "iteration_timeout_minutes": 2,
+            "max_concurrent_iterations": 2,
+            "max_cores_per_iteration": -1,
+            "n_cross_validations": 5,
+            "primary_metric": "accuracy",
+            "featurization": "off",
+            "model_explainability": False,
+            "enable_voting_ensemble": False,
+            "enable_stack_ensemble": False,
+        }
+
+    # Init experiment and compute
+    workspace, experiment = _init_experiment(
+        context=context, experiment_name=experiment_name
+    )
+
+    compute_target = init_compute(
+        context=context,
+        cpu_cluster_name=cpu_cluster_name,
+        vm_size=vm_size,
+        max_nodes=max_nodes,
+    )
+
+    # Register dataset
+    register_dataset(
+        context=context,
+        dataset_name=dataset_name,
+        dataset_description=dataset_description,
+        data=dataset,
+        create_new_version=create_new_version,
+    )
+
+    # Submit training job
+    submit_training_job(
+        context,
+        experiment=experiment,
+        compute_target=compute_target,
+        register_model_name=register_model_name,
+        registered_dataset_name=dataset_name,
+        label_column_name=label_column_name,
+        automl_settings=automl_settings,
+        training_set=dataset,
+        show_output=log_azure,
+        save_n_models=save_n_models,
+    )
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/azureml_utils/latest/src/function.yaml b/functions/master/azureml_utils/latest/src/function.yaml index f9c66bdf..a6348996 100644 --- a/functions/master/azureml_utils/latest/src/function.yaml +++ b/functions/master/azureml_utils/latest/src/function.yaml @@ -1,38 +1,32 @@ -kind: job -metadata: - name: azureml-utils - tag: '' - hash: b70ddba5204c2f52a9582abe363d9de4d5d94d52 - project: '' - labels: - author: yonish - categories: - - machine-learning - - model-training +verbose: false spec: command: '' - args: [] - image: '' build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK - base_image: python:3.9-bullseye - commands: - - apt-get update && apt-get install -y --no-install-recommends git - - apt install -y liblttng-ust0 + auto_build: true code_origin: '' - origin_filename: '' with_mlrun: true - auto_build: true requirements: - azureml-core==1.54.0.post1 - azureml-train-automl-client==1.54.0.post1 - plotly~=5.4 + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK + commands: + - apt-get update && apt-get install -y --no-install-recommends git + - apt install -y liblttng-ust0 + base_image: python:3.9-bullseye + origin_filename: '' + default_handler: train + allow_empty_resources: true + disable_auto_mount: false + image: '' entry_points: init_compute: - name: init_compute doc: 'Initialize Azure ML compute target to run experiment. Checks for existing compute target and creates new if does not exist.' + name: init_compute + lineno: 102 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -51,16 +45,17 @@ spec: outputs: - doc: Azure ML Compute Target. type: ComputeTarget - default: '' - lineno: 102 + has_varargs: false register_dataset: - name: register_dataset doc: 'Register dataset object (can be also an Iguazio FeatureVector) in Azure ML. Uploads parquet file to Azure blob storage and registers that file as a dataset in Azure ML.' + name: register_dataset + lineno: 138 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -79,12 +74,12 @@ spec: doc: Register Azure dataset as new version. Must be used when modifying dataset schema. default: false - outputs: - - default: '' - lineno: 138 + has_varargs: false download_model: - name: download_model doc: Download trained model from Azure ML to local filesystem. + name: download_model + lineno: 217 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -100,11 +95,13 @@ spec: doc: Target directory to download model. default: . outputs: - - default: '' - lineno: 217 + - type: None + has_varargs: false upload_model: - name: upload_model doc: Upload pre-trained model from local filesystem to Azure ML. + name: upload_model + lineno: 238 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -124,13 +121,15 @@ spec: doc: KV pairs of model tags. default: null outputs: - - default: '' - lineno: 238 + - type: None + has_varargs: false submit_training_job: - name: submit_training_job doc: 'Submit training job to Azure AutoML and download trained model when completed. Uses previously registered dataset for training.' + name: submit_training_job + lineno: 352 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -166,15 +165,17 @@ spec: doc: Displaying Azure logs. default: true outputs: - - default: '' - lineno: 352 + - type: None + has_varargs: false train: - name: train doc: 'Whole training flow for Azure AutoML. Registers dataset/feature vector, submits training job to Azure AutoML, and downloads trained model when completed.' + name: train + lineno: 469 + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -233,18 +234,14 @@ spec: doc: JSON string of all Azure AutoML settings. default: null outputs: - - default: '' - lineno: 469 + - type: None + has_varargs: false description: Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom. - default_handler: train - disable_auto_mount: false - allow_empty_resources: true - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false +kind: job +metadata: + categories: + - model-serving + - utils + tag: '' + name: azureml-utils diff --git a/functions/master/azureml_utils/latest/src/item.yaml b/functions/master/azureml_utils/latest/src/item.yaml index 04733b84..0b4d5e49 100644 --- a/functions/master/azureml_utils/latest/src/item.yaml +++ b/functions/master/azureml_utils/latest/src/item.yaml @@ -1,7 +1,7 @@ apiVersion: v1 categories: -- machine-learning -- model-training +- model-serving +- utils description: Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom. doc: '' @@ -13,7 +13,7 @@ labels: author: yonish maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: azureml_utils platformVersion: 3.5.3 spec: @@ -34,5 +34,5 @@ spec: - azureml-train-automl-client==1.54.0.post1 - plotly~=5.4 url: '' -version: 1.3.0 +version: 1.4.0 test_valid: True diff --git a/functions/master/azureml_utils/latest/static/azureml_utils.html b/functions/master/azureml_utils/latest/static/azureml_utils.html index 82356dfb..9a6dc0d3 100644 --- a/functions/master/azureml_utils/latest/static/azureml_utils.html +++ b/functions/master/azureml_utils/latest/static/azureml_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_utils/latest/static/documentation.html b/functions/master/azureml_utils/latest/static/documentation.html index 89713ebc..ae451127 100644 --- a/functions/master/azureml_utils/latest/static/documentation.html +++ b/functions/master/azureml_utils/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_utils/latest/static/example.html b/functions/master/azureml_utils/latest/static/example.html index e008bec6..13e6a910 100644 --- a/functions/master/azureml_utils/latest/static/example.html +++ b/functions/master/azureml_utils/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/azureml_utils/latest/static/function.html b/functions/master/azureml_utils/latest/static/function.html index f2e3e083..3aaa82f5 100644 --- a/functions/master/azureml_utils/latest/static/function.html +++ b/functions/master/azureml_utils/latest/static/function.html @@ -28,41 +28,35 @@
         
-kind: job
-metadata:
-  name: azureml-utils
-  tag: ''
-  hash: b70ddba5204c2f52a9582abe363d9de4d5d94d52
-  project: ''
-  labels:
-    author: yonish
-  categories:
-  - machine-learning
-  - model-training
+verbose: false
 spec:
   command: ''
-  args: []
-  image: ''
   build:
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK
-    base_image: python:3.9-bullseye
-    commands:
-    - apt-get update && apt-get install -y --no-install-recommends git
-    - apt install -y liblttng-ust0
+    auto_build: true
     code_origin: ''
-    origin_filename: ''
     with_mlrun: true
-    auto_build: true
     requirements:
     - azureml-core==1.54.0.post1
     - azureml-train-automl-client==1.54.0.post1
     - plotly~=5.4
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IG9zCmltcG9ydCBqc29uCmltcG9ydCBsb2dnaW5nCmZyb20gdHlwaW5nIGltcG9ydCBUdXBsZSwgTGlzdAoKZnJvbSBtbHJ1biBpbXBvcnQgTUxDbGllbnRDdHgsIERhdGFJdGVtLCBnZXRfZGF0YWl0ZW0KaW1wb3J0IG1scnVuLmZlYXR1cmVfc3RvcmUgYXMgZl9zdG9yZQppbXBvcnQgbWxydW4uZGF0YXN0b3JlCmltcG9ydCBtbHJ1bi51dGlscwpmcm9tIG1scnVuLmRhdGFzdG9yZS50YXJnZXRzIGltcG9ydCBQYXJxdWV0VGFyZ2V0Cgpmcm9tIGF6dXJlbWwuY29yZS5hdXRoZW50aWNhdGlvbiBpbXBvcnQgU2VydmljZVByaW5jaXBhbEF1dGhlbnRpY2F0aW9uCmZyb20gYXp1cmVtbC5jb3JlLndvcmtzcGFjZSBpbXBvcnQgV29ya3NwYWNlCmZyb20gYXp1cmVtbC5jb3JlLmV4cGVyaW1lbnQgaW1wb3J0IEV4cGVyaW1lbnQKZnJvbSBhenVyZW1sLmNvcmUuZGF0YXNldCBpbXBvcnQgRGF0YXNldApmcm9tIGF6dXJlbWwuY29yZS5tb2RlbCBpbXBvcnQgTW9kZWwKZnJvbSBhenVyZW1sLmNvcmUuY29tcHV0ZSBpbXBvcnQgQ29tcHV0ZVRhcmdldCwgQW1sQ29tcHV0ZQpmcm9tIGF6dXJlbWwuY29yZS5jb21wdXRlX3RhcmdldCBpbXBvcnQgQ29tcHV0ZVRhcmdldEV4Y2VwdGlvbgpmcm9tIGF6dXJlbWwuY29yZS5zY3JpcHRfcnVuIGltcG9ydCBTY3JpcHRSdW4KCmZyb20gYXp1cmVtbC50cmFpbi5hdXRvbWwgaW1wb3J0IEF1dG9NTENvbmZpZwpmcm9tIGF6dXJlbWwudHJhaW4uYXV0b21sLnJ1biBpbXBvcnQgQXV0b01MUnVuCgoKZGVmIF9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsIGtleSk6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbjoKICAgICAgICByZXR1cm4gb3MuZW52aXJvbltrZXldCiAgICByZXR1cm4gY29udGV4dC5nZXRfc2VjcmV0KGtleSkKCgpkZWYgX2xvYWRfd29ya3NwYWNlKGNvbnRleHQ6IE1MQ2xpZW50Q3R4KSAtPiBXb3Jrc3BhY2U6CiAgICAiIiIKICAgIExvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2Ugd2l0aCBBenVyZSBzZWNyZXRzLgoKICAgIDpwYXJhbSBjb250ZXh0OiBNTFJ1biBjb250ZXh0LgogICAgOnJldHVybnM6ICAgICAgIEF6dXJlTUwgV29ya3NwYWNlCiAgICAiIiIKCiAgICBpZiBoYXNhdHRyKGNvbnRleHQsICJfYXp1cmVfd29ya3NwYWNlIik6CiAgICAgICAgcmV0dXJuIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkxvYWRpbmcgQXp1cmVNTCBXb3Jrc3BhY2UiKQogICAgIyBBenVyZSBzZXJ2aWNlIGF1dGhlbnRpY2F0aW9uOgogICAgc2VydmljZV9hdXRoZW50aWNhdGlvbiA9IFNlcnZpY2VQcmluY2lwYWxBdXRoZW50aWNhdGlvbigKICAgICAgICB0ZW5hbnRfaWQ9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1RFTkFOVF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX2lkPV9lbnZfb3Jfc2VjcmV0KGNvbnRleHQsICJBWlVSRV9TRVJWSUNFX1BSSU5DSVBBTF9JRCIpLAogICAgICAgIHNlcnZpY2VfcHJpbmNpcGFsX3Bhc3N3b3JkPV9lbnZfb3Jfc2VjcmV0KAogICAgICAgICAgICBjb250ZXh0LCAiQVpVUkVfU0VSVklDRV9QUklOQ0lQQUxfUEFTU1dPUkQiCiAgICAgICAgKSwKICAgICkKCiAgICAjIExvYWRpbmcgQXp1cmUgd29ya3NwYWNlOgogICAgd29ya3NwYWNlID0gV29ya3NwYWNlKAogICAgICAgIHN1YnNjcmlwdGlvbl9pZD1fZW52X29yX3NlY3JldChjb250ZXh0LCAiQVpVUkVfU1VCU0NSSVBUSU9OX0lEIiksCiAgICAgICAgcmVzb3VyY2VfZ3JvdXA9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1JFU09VUkNFX0dST1VQIiksCiAgICAgICAgd29ya3NwYWNlX25hbWU9X2Vudl9vcl9zZWNyZXQoY29udGV4dCwgIkFaVVJFX1dPUktTUEFDRV9OQU1FIiksCiAgICAgICAgYXV0aD1zZXJ2aWNlX2F1dGhlbnRpY2F0aW9uLAogICAgKQoKICAgIGNvbnRleHQuX2F6dXJlX3dvcmtzcGFjZSA9IHdvcmtzcGFjZQogICAgcmV0dXJuIHdvcmtzcGFjZQoKCmRlZiBfaW5pdF9leHBlcmltZW50KAogICAgY29udGV4dDogTUxDbGllbnRDdHgsIGV4cGVyaW1lbnRfbmFtZTogc3RyCikgLT4gVHVwbGVbV29ya3NwYWNlLCBFeHBlcmltZW50XToKICAgICIiIgogICAgSW5pdGlhbGl6ZSB3b3Jrc3BhY2UgYW5kIGV4cGVyaW1lbnQgaW4gQXp1cmUgTUwuIFVzZXMgU2VydmljZQogICAgUHJpbmNpcGFsIGF1dGhlbnRpY2F0aW9uIHZpYSBlbnZpcm9ubWVudCB2YXJpYWJsZXMuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBleHBlcmltZW50X25hbWU6IE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICBBenVyZSBNTCBXb3Jrc3BhY2UgYW5kIEV4cGVyaW1lbnQuCiAgICAiIiIKCiAgICAjIEluaXRpYWxpemUgZXhwZXJpbWVudCB2aWEgU2VydmljZSBQcmluY2lwYWwgQXV0aGVudGljYXRpb246CiAgICAjIGh0dHBzOi8vZG9jcy5taWNyb3NvZnQuY29tL2VuLXVzL2F6dXJlL21hY2hpbmUtbGVhcm5pbmcvaG93LXRvLXNldHVwLWF1dGhlbnRpY2F0aW9uI3VzZS1zZXJ2aWNlLXByaW5jaXBhbC1hdXRoZW50aWNhdGlvbgoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBleHBlcmltZW50IHtleHBlcmltZW50X25hbWV9IikKICAgICMgQ3JlYXRpbmcgZXhwZXJpbWVudDoKICAgIGV4cGVyaW1lbnQgPSBFeHBlcmltZW50KHdvcmtzcGFjZSwgZXhwZXJpbWVudF9uYW1lKQoKICAgIHJldHVybiB3b3Jrc3BhY2UsIGV4cGVyaW1lbnQKCgpkZWYgaW5pdF9jb21wdXRlKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBjcHVfY2x1c3Rlcl9uYW1lOiBzdHIsCiAgICB2bV9zaXplOiBzdHIgPSAiU1RBTkRBUkRfRDJfVjIiLAogICAgbWF4X25vZGVzOiBpbnQgPSAxLAopIC0+IENvbXB1dGVUYXJnZXQ6CiAgICAiIiIKICAgIEluaXRpYWxpemUgQXp1cmUgTUwgY29tcHV0ZSB0YXJnZXQgdG8gcnVuIGV4cGVyaW1lbnQuIENoZWNrcyBmb3IKICAgIGV4aXN0aW5nIGNvbXB1dGUgdGFyZ2V0IGFuZCBjcmVhdGVzIG5ldyBpZiBkb2VzIG5vdCBleGlzdC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgTUxSdW4gY29udGV4dC4KICAgIDpwYXJhbSBjcHVfY2x1c3Rlcl9uYW1lOiBOYW1lIG9mIEF6dXJlIE1MIGNvbXB1dGUgdGFyZ2V0LiBDcmVhdGVkIGlmIGRvZXMgbm90IGV4aXN0LgogICAgOnBhcmFtIHZtX3NpemU6ICAgICAgICAgIEF6dXJlIG1hY2hpbmUgdHlwZSBmb3IgY29tcHV0ZSB0YXJnZXQuCiAgICA6cGFyYW0gbWF4X25vZGVzOiAgICAgICAgTWF4aW11bSBudW1iZXIgb2YgY29uY3VycmVudCBjb21wdXRlIHRhcmdldHMuCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgQXp1cmUgTUwgQ29tcHV0ZSBUYXJnZXQuCiAgICAiIiIKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJJbml0aWFsaXppbmcgQXp1cmVNTCBjb21wdXRlIHRhcmdldCB7Y3B1X2NsdXN0ZXJfbmFtZX0iKQoKICAgICMgVmVyaWZ5IHRoYXQgY2x1c3RlciBkb2VzIG5vdCBleGlzdCBhbHJlYWR5OgogICAgdHJ5OgogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldCh3b3Jrc3BhY2U9d29ya3NwYWNlLCBuYW1lPWNwdV9jbHVzdGVyX25hbWUpCiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiRm91bmQgZXhpc3RpbmcgY2x1c3Rlciwgd2lsbCB1c2UgaXQuIikKICAgIGV4Y2VwdCBDb21wdXRlVGFyZ2V0RXhjZXB0aW9uOgogICAgICAgIGNvbXB1dGVfY29uZmlnID0gQW1sQ29tcHV0ZS5wcm92aXNpb25pbmdfY29uZmlndXJhdGlvbigKICAgICAgICAgICAgdm1fc2l6ZT12bV9zaXplLCBtYXhfbm9kZXM9bWF4X25vZGVzCiAgICAgICAgKQogICAgICAgIGNvbXB1dGVfdGFyZ2V0ID0gQ29tcHV0ZVRhcmdldC5jcmVhdGUoCiAgICAgICAgICAgIHdvcmtzcGFjZSwgY3B1X2NsdXN0ZXJfbmFtZSwgY29tcHV0ZV9jb25maWcKICAgICAgICApCgogICAgY29tcHV0ZV90YXJnZXQud2FpdF9mb3JfY29tcGxldGlvbihzaG93X291dHB1dD1UcnVlKQogICAgcmV0dXJuIGNvbXB1dGVfdGFyZ2V0CgoKZGVmIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgIGRhdGFzZXRfbmFtZTogc3RyLAogICAgZGF0YXNldF9kZXNjcmlwdGlvbjogc3RyLAogICAgZGF0YTogRGF0YUl0ZW0sCiAgICBjcmVhdGVfbmV3X3ZlcnNpb246IGJvb2wgPSBGYWxzZSwKKToKICAgICIiIgogICAgUmVnaXN0ZXIgZGF0YXNldCBvYmplY3QgKGNhbiBiZSBhbHNvIGFuIElndWF6aW8gRmVhdHVyZVZlY3RvcikgaW4gQXp1cmUgTUwuCiAgICBVcGxvYWRzIHBhcnF1ZXQgZmlsZSB0byBBenVyZSBibG9iIHN0b3JhZ2UgYW5kIHJlZ2lzdGVycwogICAgdGhhdCBmaWxlIGFzIGEgZGF0YXNldCBpbiBBenVyZSBNTC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXRfbmFtZTogICAgICAgICAgTmFtZSBvZiBBenVyZSBkYXRhc2V0IHRvIHJlZ2lzdGVyLgogICAgOnBhcmFtIGRhdGFzZXRfZGVzY3JpcHRpb246ICAgRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KICAgIDpwYXJhbSBkYXRhOiAgICAgICAgICAgICAgICAgIE1MUnVuIEZlYXR1cmVWZWN0b3Igb3IgZGF0YXNldCBvYmplY3QgdG8gdXBsb2FkLgogICAgOnBhcmFtIGNyZWF0ZV9uZXdfdmVyc2lvbjogICAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGlmeWluZyBkYXRhc2V0IHNjaGVtYS4KICAgICIiIgoKICAgICMgdGVzdCBmb3IgQXp1cmUgc3RvcmFnZSBjb25uZWN0aW9uIGVudmlyb25tZW50IHZhcmlhYmxlIG9yIHNlY3JldDoKICAgIGFzc2VydCBfZW52X29yX3NlY3JldCgKICAgICAgICBjb250ZXh0LCAiQVpVUkVfU1RPUkFHRV9DT05ORUNUSU9OX1NUUklORyIKICAgICksICJBWlVSRV9TVE9SQUdFX0NPTk5FQ1RJT05fU1RSSU5HIHNlY3JldCBub3Qgc2V0IgoKICAgICMgQ29ubmVjdCB0byBBenVyZU1MIGV4cGVyaW1lbnQgYW5kIGRhdGFzdG9yZToKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbm5lY3RpbmcgdG8gQXp1cmVNTCBleHBlcmltZW50IGRlZmF1bHQgZGF0YXN0b3JlIikKCiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGRhdGFzdG9yZSA9IHdvcmtzcGFjZS5nZXRfZGVmYXVsdF9kYXRhc3RvcmUoKQoKICAgICMgQXp1cmUgYmxvYiBwYXRoIChkZWZhdWx0IGRhdGFzdG9yZSBmb3Igd29ya3NwYWNlKToKICAgIGJsb2JfcGF0aCA9IGYiYXo6Ly97ZGF0YXN0b3JlLmNvbnRhaW5lcl9uYW1lfS97ZGF0YXNldF9uYW1lfSIKCiAgICBzdG9yZV91cmlfcHJlZml4LCBfID0gbWxydW4uZGF0YXN0b3JlLnBhcnNlX3N0b3JlX3VyaShkYXRhLmFydGlmYWN0X3VybCkKICAgIGZlYXR1cmVfdmVjdG9yX2Nhc2UgPSBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXgKICAgICMgUmV0cmlldmUgZGF0YSBzb3VyY2UgYXMgZGF0YWZyYW1lOgogICAgaWYgZmVhdHVyZV92ZWN0b3JfY2FzZToKICAgICAgICAjIEZlYXR1cmVWZWN0b3IgY2FzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIlJldHJpZXZpbmcgZmVhdHVyZSB2ZWN0b3IgYW5kIHVwbG9hZGluZyB0byBBenVyZSBibG9iIHN0b3JhZ2U6IHtibG9iX3BhdGh9IgogICAgICAgICkKICAgICAgICBmX3N0b3JlLmdldF9vZmZsaW5lX2ZlYXR1cmVzKGRhdGEubWV0YS51cmksIHRhcmdldD1QYXJxdWV0VGFyZ2V0KHBhdGg9YmxvYl9wYXRoKSkKICAgIGVsc2U6CiAgICAgICAgYmxvYl9wYXRoICs9IGRhdGEuc3VmZml4CiAgICAgICAgIyBEYXRhSXRlbSBjYXNlOgogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmV0cmlldmluZyBmZWF0dXJlIHZlY3RvciBhbmQgdXBsb2FkaW5nIHRvIEF6dXJlIGJsb2Igc3RvcmFnZToge2Jsb2JfcGF0aH0iCiAgICAgICAgKQogICAgICAgIGRhdGFfaW5fYnl0ZXMgPSBkYXRhLmdldCgpCiAgICAgICAgZ2V0X2RhdGFpdGVtKGJsb2JfcGF0aCkucHV0KGRhdGFfaW5fYnl0ZXMpCgogICAgIyBSZWdpc3RlciBkYXRhc2V0IGluIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiUmVnaXN0ZXJpbmcgZGF0YXNldCB7ZGF0YXNldF9uYW1lfSBpbiBBenVyZSBNTCIpCiAgICBpZiBkYXRhLnN1ZmZpeCA9PSAiLnBhcnF1ZXQiIG9yIGZlYXR1cmVfdmVjdG9yX2Nhc2U6CiAgICAgICAgZGF0YXNldCA9IERhdGFzZXQuVGFidWxhci5mcm9tX3BhcnF1ZXRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfS5wYXJxdWV0IiksIHZhbGlkYXRlPUZhbHNlCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICBmIk9wZW5TU0wgdmVyc2lvbiBtdXN0IGJlIDEuMS4gT3ZlcnJpZGluZyB0aGUgT3BlblNTTCB2ZXJzaW9uIHRvIDEuMSIKICAgICAgICApCiAgICAgICAgIyBPcGVuU1NMIHZlcnNpb24gbXVzdCBiZSAxLjEKICAgICAgICBvcy5lbnZpcm9uWyJDTFJfT1BFTlNTTF9WRVJTSU9OX09WRVJSSURFIl0gPSAiMS4xIgogICAgICAgIGRhdGFzZXQgPSBEYXRhc2V0LlRhYnVsYXIuZnJvbV9kZWxpbWl0ZWRfZmlsZXMoCiAgICAgICAgICAgIHBhdGg9KGRhdGFzdG9yZSwgZiJ7ZGF0YXNldF9uYW1lfXtkYXRhLnN1ZmZpeH0iKSwgdmFsaWRhdGU9RmFsc2UKICAgICAgICApCgogICAgZGF0YXNldC5yZWdpc3RlcigKICAgICAgICB3b3Jrc3BhY2U9d29ya3NwYWNlLAogICAgICAgIG5hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPWRhdGFzZXRfZGVzY3JpcHRpb24sCiAgICAgICAgY3JlYXRlX25ld192ZXJzaW9uPWNyZWF0ZV9uZXdfdmVyc2lvbiwKICAgICkKCiAgICAjIE91dHB1dCByZWdpc3RlcmVkIGRhdGFzZXQgbmFtZSBpbiBBenVyZToKICAgIGNvbnRleHQubG9nX3Jlc3VsdCgiZGF0YXNldF9ibG9iX3BhdGgiLCBibG9iX3BhdGgpCgoKZGVmIGRvd25sb2FkX21vZGVsKAogICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICBtb2RlbF92ZXJzaW9uOiBpbnQsCiAgICB0YXJnZXRfZGlyOiBzdHIgPSAiLiIsCikgLT4gTm9uZToKICAgICIiIgogICAgRG93bmxvYWQgdHJhaW5lZCBtb2RlbCBmcm9tIEF6dXJlIE1MIHRvIGxvY2FsIGZpbGVzeXN0ZW0uCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgTmFtZSBvZiB0cmFpbmVkIGFuZCByZWdpc3RlcmVkIG1vZGVsLgogICAgOnBhcmFtIG1vZGVsX3ZlcnNpb246IFZlcnNpb24gb2YgbW9kZWwgdG8gZG93bmxvYWQuCiAgICA6cGFyYW0gdGFyZ2V0X2RpcjogICAgVGFyZ2V0IGRpcmVjdG9yeSB0byBkb3dubG9hZCBtb2RlbC4KICAgICIiIgogICAgIyBMb2FkaW5nIHdvcmtzcGFjZSBpZiBub3QgcHJvdmlkZWQ6CiAgICB3b3Jrc3BhY2UgPSBfbG9hZF93b3Jrc3BhY2UoY29udGV4dCkKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJEb3dubG9hZGluZyBtb2RlbCB7bW9kZWxfbmFtZX06e21vZGVsX3ZlcnNpb259IikKICAgIG1vZGVsID0gTW9kZWwod29ya3NwYWNlLCBtb2RlbF9uYW1lLCB2ZXJzaW9uPW1vZGVsX3ZlcnNpb24pCiAgICBtb2RlbC5kb3dubG9hZCh0YXJnZXRfZGlyPXRhcmdldF9kaXIsIGV4aXN0X29rPVRydWUpCgoKZGVmIHVwbG9hZF9tb2RlbCgKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbW9kZWxfZGVzY3JpcHRpb246IHN0ciA9IE5vbmUsCiAgICBtb2RlbF90YWdzOiBkaWN0ID0gTm9uZSwKKSAtPiBOb25lOgogICAgIiIiCiAgICBVcGxvYWQgcHJlLXRyYWluZWQgbW9kZWwgZnJvbSBsb2NhbCBmaWxlc3lzdGVtIHRvIEF6dXJlIE1MLgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICBOYW1lIG9mIHRyYWluZWQgYW5kIHJlZ2lzdGVyZWQgbW9kZWwuCiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFBhdGggdG8gZmlsZSBvbiBsb2NhbCBmaWxlc3lzdGVtLgogICAgOnBhcmFtIG1vZGVsX2Rlc2NyaXB0aW9uOiBEZXNjcmlwdGlvbiBvZiBtb2RlbHMuCiAgICA6cGFyYW0gbW9kZWxfdGFnczogICAgICAgIEtWIHBhaXJzIG9mIG1vZGVsIHRhZ3MuCiAgICAiIiIKICAgICMgTG9hZGluZyB3b3Jrc3BhY2UgaWYgbm90IHByb3ZpZGVkOgogICAgd29ya3NwYWNlID0gX2xvYWRfd29ya3NwYWNlKGNvbnRleHQpCgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIlVwbG9hZCBtb2RlbCB7bW9kZWxfbmFtZX0gZnJvbSB7bW9kZWxfcGF0aH0iKQogICAgTW9kZWwucmVnaXN0ZXIoCiAgICAgICAgd29ya3NwYWNlPXdvcmtzcGFjZSwKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRlc2NyaXB0aW9uPW1vZGVsX2Rlc2NyaXB0aW9uLAogICAgICAgIHRhZ3M9bW9kZWxfdGFncywKICAgICkKCgpkZWYgX2dldF90b3Bfbl9ydW5zKAogICAgcmVtb3RlX3J1bjogQXV0b01MUnVuLCBuOiBpbnQgPSA1LCBwcmltYXJ5X21ldHJpYzogc3RyID0gImFjY3VyYWN5IgopIC0+IExpc3RbU2NyaXB0UnVuXToKICAgICIiIgogICAgR2V0IHRvcCBOIGNvbXBsZXRlIHJ1bnMgZnJvbSBleHBlcmltZW50IHNvcnRlZCBieSBwcmltYXJ5IG1ldHJpYy4KCiAgICA6cGFyYW0gcmVtb3RlX3J1bjogICAgIEF6dXJlIE1MIFJ1bi4KICAgIDpwYXJhbSBuOiAgICAgICAgICAgICAgTnVtYmVyIG9mIHRvcCBydW5zIHRvIHJldHVybi4KICAgIDpwYXJhbSBwcmltYXJ5X21ldHJpYzogTWV0cmljIHRvIHNvcnQgYnkuCgogICAgOnJldHVybnM6ICAgICAgICAgICAgICBMaXN0IG9mIHRvcCBOIHJ1bnMgc29ydGVkIGJ5IHByaW1hcnkgbWV0cmljLgogICAgIiIiCiAgICAjIENvbGxlY3QgYWxsIG1vZGVsczoKICAgIGNvbXBsZXRlX3J1bnMgPSBbCiAgICAgICAgcnVuCiAgICAgICAgZm9yIHJ1biBpbiByZW1vdGVfcnVuLmdldF9jaGlsZHJlbihzdGF0dXM9IkNvbXBsZXRlZCIpCiAgICAgICAgaWYgbm90IGFueShzIGluIHJ1bi5pZCBmb3IgcyBpbiBbInNldHVwIiwgIndvcmtlciJdKQogICAgXQoKICAgICMgQ2hlY2tpbmcgdGhhdCB0aGUgcmVxdWlyZWQgbnVtYmVyIG9mIHJ1bnMgYXJlIGRvbmU6CiAgICBpZiBsZW4oY29tcGxldGVfcnVucykgPCBuOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJFeHBlY3RlZCB7bn0gcnVucyBidXQgb25seSByZWNlaXZlZCB7bGVuKGNvbXBsZXRlX3J1bnMpfSIpCgogICAgIyBTb3J0aW5nIGJ5IHRoZSBwcmltYXJ5IG1ldHJpYzoKICAgIHNvcnRlZF9ydW5zID0gc29ydGVkKAogICAgICAgIGNvbXBsZXRlX3J1bnMsIGtleT1sYW1iZGEgcnVuOiBydW4uZ2V0X21ldHJpY3MoKVtwcmltYXJ5X21ldHJpY10sIHJldmVyc2U9VHJ1ZQogICAgKQogICAgcmV0dXJuIHNvcnRlZF9ydW5zWzpuXQoKCmRlZiBfZ2V0X21vZGVsX2hwKAogICAgcnVuOiBTY3JpcHRSdW4sCikgLT4gZGljdDoKICAgICIiIgogICAgR2V0IGh5cGVyLXBhcmFtZXRlcnMgb2YgdHJhaW5lZCBBenVyZU1MIG1vZGVsLgogICAgQ29tYmluZSB0aGUgaHlwZXItcGFyYW1ldGVycyBvZiB0aGUgZGF0YSB0cmFuc2Zvcm1hdGlvbiBhbmQgdHJhaW5pbmcgdG8gYSBkaWN0aW9uYXJ5LgogICAgVGhlIHByZWZpeCBvZiB0aGUgZGljdGlvbmFyeSBrZXlzIGNvcnJlc3BvbmRzIHRvICdkYXRhIHRyYW5zZm9ybWF0aW9uJyBhbmQgJ3RyYWluaW5nJy4KCiAgICA6cGFyYW0gcnVuOiBSdW4gb2JqZWN0IG9mIEF6dXJlTUwgdHJhaW5lZCBtb2RlbC4KCiAgICA6cmV0dXJuczogICAgQSBkaWN0aW9uYXJ5IGFzIGRlc2NyaWJlZCBpbiB0aGUgZG9jc3RyaW5nLgogICAgIiIiCgogICAgc3BlY19maWVsZCA9ICJwaXBlbGluZV9zcGVjIgogICAgaWYgc3BlY19maWVsZCBub3QgaW4gcnVuLnByb3BlcnRpZXM6CiAgICAgICAgcmV0dXJuIHt9CiAgICBzcGVjX3N0cmluZyA9IHJ1bi5wcm9wZXJ0aWVzW3NwZWNfZmllbGRdCiAgICBzcGVjX2RpY3QgPSBqc29uLmxvYWRzKHNwZWNfc3RyaW5nKQoKICAgIGlmICJvYmplY3RzIiBub3QgaW4gc3BlY19kaWN0OgogICAgICAgICMgTm8gaHlwZXItcGFyYW1zCiAgICAgICAgcmV0dXJuIHt9CiAgICBocF9kaWN0cyA9IHNwZWNfZGljdFsib2JqZWN0cyJdCiAgICAjIGFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3Q6CiAgICBhc3NlcnQgKAogICAgICAgIGxlbihocF9kaWN0cykgPT0gMgogICAgKSwgImFmdGVyIHRyYWluaW5nIHRoZXJlIGFyZSB0d28gaHlwZXItcGFyYW1ldGVycyBkaWN0cyBpbnNpZGUgdGhlIHJ1biBvYmplY3QiCiAgICByZXN1bHRfZGljdCA9IHt9CiAgICBkaWN0X2tleXMgPSBbCiAgICAgICAgWyJkYXRhX3RyYW5zX2NsYXNzX25hbWUiLCAiZGF0YV90cmFuc19tb2R1bGUiLCAiZGF0YV90cmFuc19zcGVjX2NsYXNzIl0sCiAgICAgICAgWwogICAgICAgICAgICAidHJhaW5fY2xhc3NfbmFtZSIsCiAgICAgICAgICAgICJ0cmFpbl9tb2R1bGUiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX0MiLAogICAgICAgICAgICAidHJhaW5fcGFyYW1fa3dhcmdzX2NsYXNzX3dlaWdodCIsCiAgICAgICAgICAgICJ0cmFpbl9zcGVjX2NsYXNzIiwKICAgICAgICBdLAogICAgXQoKICAgICMgY3JlYXRpbmcgaHlwZXItcGFyYW1zIGRpY3Qgd2l0aCBrZXkgcHJlZml4ZXMgZm9yIGVhY2ggcGFydDoKICAgIGt3YXJnc19wcmVmaXggPSAicGFyYW1fa3dhcmdzIgogICAgZm9yIGQsIG5hbWUsIGtleXMgaW4gemlwKGhwX2RpY3RzLCBbImRhdGFfdHJhbnMiLCAidHJhaW4iXSwgZGljdF9rZXlzKToKICAgICAgICBmb3Iga2V5IGluIGtleXM6CgogICAgICAgICAgICBpZiBrd2FyZ3NfcHJlZml4IGluIGtleToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2t3YXJnc19wcmVmaXhdWwogICAgICAgICAgICAgICAgICAgIGtleS5yZXBsYWNlKGYie25hbWV9X3trd2FyZ3NfcHJlZml4fV8iLCAiIikKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdF9kaWN0W2tleV0gPSBkW2tleS5yZXBsYWNlKGYie25hbWV9XyIsICIiKV0KICAgICAgICAgICAgaWYgbm90IHJlc3VsdF9kaWN0W2tleV06CiAgICAgICAgICAgICAgICByZXN1bHRfZGljdFtrZXldID0gIiIKCiAgICByZXR1cm4gcmVzdWx0X2RpY3QKCgpkZWYgc3VibWl0X3RyYWluaW5nX2pvYigKICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZXhwZXJpbWVudDogRXhwZXJpbWVudCwKICAgIGNvbXB1dGVfdGFyZ2V0OiBDb21wdXRlVGFyZ2V0LAogICAgcmVnaXN0ZXJfbW9kZWxfbmFtZTogc3RyLAogICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU6IHN0ciwKICAgIGF1dG9tbF9zZXR0aW5nczogZGljdCwKICAgIHRyYWluaW5nX3NldDogRGF0YUl0ZW0sCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gJycsCiAgICBzYXZlX25fbW9kZWxzOiBpbnQgPSAzLAogICAgc2hvd19vdXRwdXQ6IGJvb2wgPSBUcnVlLAopIC0+IE5vbmU6CiAgICAiIiIKICAgIFN1Ym1pdCB0cmFpbmluZyBqb2IgdG8gQXp1cmUgQXV0b01MIGFuZCBkb3dubG9hZCB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4gVXNlcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgZGF0YXNldCBmb3IgdHJhaW5pbmcuCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGV4cGVyaW1lbnQ6ICAgICAgICAgICAgICBBenVyZSBleHBlcmltZW50LgogICAgOnBhcmFtIGNvbXB1dGVfdGFyZ2V0OiAgICAgICAgICBBenVyZSBjb21wdXRlIHRhcmdldC4KICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiAgICAgTmFtZSBvZiBtb2RlbCB0byByZWdpc3RlciBpbiBBenVyZS4KICAgIDpwYXJhbSByZWdpc3RlcmVkX2RhdGFzZXRfbmFtZTogTmFtZSBvZiBkYXRhc2V0IHJlZ2lzdGVyZWQgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgICAgIE5hbWUgb2YgdGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgogICAgOnBhcmFtIGF1dG9tbF9zZXR0aW5nczogICAgICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgOnBhcmFtIHRyYWluaW5nX3NldDogICAgICAgICAgICBUcmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWwuIEZvciBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb25pdG9yaW5nIGludGVncmF0aW9uLgogICAgOnBhcmFtIHNob3dfb3V0cHV0OiAgICAgICAgICAgICBEaXNwbGF5aW5nIEF6dXJlIGxvZ3MuCiAgICA6cGFyYW0gc2F2ZV9uX21vZGVsczogICAgICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgIiIiCiAgICAjIExvYWRpbmcgd29ya3NwYWNlIGlmIG5vdCBwcm92aWRlZDoKICAgIHdvcmtzcGFjZSA9IF9sb2FkX3dvcmtzcGFjZShjb250ZXh0KQoKICAgICMgU2V0dXAgZXhwZXJpbWVudDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIlNldHRpbmcgdXAgZXhwZXJpbWVudCBwYXJhbWV0ZXJzIikKICAgIGRhdGFzZXQgPSBEYXRhc2V0LmdldF9ieV9uYW1lKHdvcmtzcGFjZSwgbmFtZT1yZWdpc3RlcmVkX2RhdGFzZXRfbmFtZSkKCiAgICAjIEdldCB0cmFpbmluZyBzZXQgdG8gbG9nIHdpdGggbW9kZWw6CiAgICBmZWF0dXJlX3ZlY3RvciA9IE5vbmUKICAgIHN0b3JlX3VyaV9wcmVmaXgsIF8gPSBtbHJ1bi5kYXRhc3RvcmUucGFyc2Vfc3RvcmVfdXJpKHRyYWluaW5nX3NldC5hcnRpZmFjdF91cmwpCiAgICBpZiBtbHJ1bi51dGlscy5TdG9yZVByZWZpeC5GZWF0dXJlVmVjdG9yID09IHN0b3JlX3VyaV9wcmVmaXg6CiAgICAgICAgZmVhdHVyZV92ZWN0b3IgPSB0cmFpbmluZ19zZXQubWV0YS51cmkKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZSA9IGxhYmVsX2NvbHVtbl9uYW1lIG9yIHRyYWluaW5nX3NldC5tZXRhLnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnbGFiZWwgY29sdW1uIG5hbWU6IHtsYWJlbF9jb2x1bW5fbmFtZX0nKQogICAgICAgIHRyYWluaW5nX3NldCA9IGZfc3RvcmUuZ2V0X29mZmxpbmVfZmVhdHVyZXMoZmVhdHVyZV92ZWN0b3IpLnRvX2RhdGFmcmFtZSgpCiAgICBlbHNlOgogICAgICAgIHRyYWluaW5nX3NldCA9IHRyYWluaW5nX3NldC5hc19kZigpCgogICAgYXV0b21sX2NvbmZpZyA9IEF1dG9NTENvbmZpZygKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICB0cmFpbmluZ19kYXRhPWRhdGFzZXQsCiAgICAgICAgdmVyYm9zaXR5PWxvZ2dpbmcuSU5GTywKICAgICAgICBsYWJlbF9jb2x1bW5fbmFtZT1sYWJlbF9jb2x1bW5fbmFtZSwKICAgICAgICAqKmF1dG9tbF9zZXR0aW5ncywKICAgICkKCiAgICAjIFJ1biBleHBlcmltZW50IG9uIEF6dXJlTUw6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJTdWJtaXR0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnQiKQogICAgcmVtb3RlX3J1biA9IGV4cGVyaW1lbnQuc3VibWl0KGF1dG9tbF9jb25maWcpCiAgICByZW1vdGVfcnVuLndhaXRfZm9yX2NvbXBsZXRpb24oc2hvd19vdXRwdXQ9c2hvd19vdXRwdXQpCiAgICBpZiBzaG93X291dHB1dDoKICAgICAgICAjIEF6dXJlIGxvZyBlbmRpbmcgcm93OgogICAgICAgIHByaW50KGYiXG57JyonICogOTJ9XG4iKQogICAgIyBHZXQgdG9wIE4gcnVucyB0byBsb2c6CiAgICB0b3BfcnVucyA9IF9nZXRfdG9wX25fcnVucygKICAgICAgICByZW1vdGVfcnVuPXJlbW90ZV9ydW4sCiAgICAgICAgbj1zYXZlX25fbW9kZWxzLAogICAgICAgIHByaW1hcnlfbWV0cmljPWF1dG9tbF9zZXR0aW5nc1sicHJpbWFyeV9tZXRyaWMiXSwKICAgICkKCiAgICAjIFJlZ2lzdGVyLCBkb3dubG9hZCwgYW5kIGxvZyBtb2RlbHM6CiAgICBmb3IgaSwgcnVuIGluIGVudW1lcmF0ZSh0b3BfcnVucyk6CiAgICAgICAgIyBSZWdpc3RlciBtb2RlbDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJSZWdpc3RlcmluZyBtb2RlbCIpCiAgICAgICAgbW9kZWwgPSBydW4ucmVnaXN0ZXJfbW9kZWwoCiAgICAgICAgICAgIG1vZGVsX25hbWU9cmVnaXN0ZXJfbW9kZWxfbmFtZSwgbW9kZWxfcGF0aD0ib3V0cHV0cy9tb2RlbC5wa2wiCiAgICAgICAgKQogICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgIGYiUmVnaXN0ZXJlZCBtb2RlbCB3aXRoIG5hbWUgJ3ttb2RlbC5uYW1lfScsIGlkICd7bW9kZWwuaWR9JywgdmVyc2lvbiAne21vZGVsLnZlcnNpb259JyIKICAgICAgICApCgogICAgICAgICMgRG93bmxvYWQgbW9kZWwgbG9jYWxseToKICAgICAgICBkb3dubG9hZF9tb2RlbCgKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgICAgIG1vZGVsX3ZlcnNpb249bW9kZWwudmVyc2lvbiwKICAgICAgICAgICAgdGFyZ2V0X2Rpcj1mIi4ve21vZGVsLnZlcnNpb259IiwKICAgICAgICApCgogICAgICAgIG1ldHJpY3MgPSB7ay5sb3dlcigpOiB2YWwgZm9yIGssIHZhbCBpbiBydW4uZ2V0X21ldHJpY3MoKS5pdGVtcygpfQogICAgICAgIGRlbCBtZXRyaWNzWyJjb25mdXNpb25fbWF0cml4Il0KICAgICAgICBkZWwgbWV0cmljc1siYWNjdXJhY3lfdGFibGUiXQoKICAgICAgICAjIENvbGxlY3QgbW9kZWwgaHlwZXItcGFyYW1ldGVyczoKICAgICAgICBtb2RlbF9ocF9kaWN0ID0gX2dldF9tb2RlbF9ocChydW4pCiAgICAgICAgd2l0aCBjb250ZXh0LmdldF9jaGlsZF9jb250ZXh0KCoqbW9kZWxfaHBfZGljdCkgYXMgY2hpbGQ6CiAgICAgICAgICAgIG1vZGVsX2tleSA9IGYibW9kZWxfe2kgKyAxfV97bW9kZWxfaHBfZGljdFsnZGF0YV90cmFuc19jbGFzc19uYW1lJ10ubG93ZXIoKX1fe21vZGVsX2hwX2RpY3RbJ3RyYWluX2NsYXNzX25hbWUnXS5sb3dlcigpfSIKICAgICAgICAgICAgIyBMb2cgbW9kZWw6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICBmIkxvZ2dpbmcge21vZGVsX2tleX0gbW9kZWwgdG8gTUxSdW4iCiAgICAgICAgICAgICkKICAgICAgICAgICAgY2hpbGQubG9nX3Jlc3VsdHMobWV0cmljcykKICAgICAgICAgICAgY2hpbGQubG9nX21vZGVsKAogICAgICAgICAgICAgICAgIm1vZGVsIiwKICAgICAgICAgICAgICAgIGRiX2tleT1tb2RlbF9rZXksCiAgICAgICAgICAgICAgICBhcnRpZmFjdF9wYXRoPWNvbnRleHQuYXJ0aWZhY3Rfc3VicGF0aCgibW9kZWxzIiksCiAgICAgICAgICAgICAgICBtZXRyaWNzPW1ldHJpY3MsCiAgICAgICAgICAgICAgICBtb2RlbF9maWxlPWYie21vZGVsLnZlcnNpb259L21vZGVsLnBrbCIsCiAgICAgICAgICAgICAgICB0cmFpbmluZ19zZXQ9dHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgICAgICAgICAgZmVhdHVyZV92ZWN0b3I9ZmVhdHVyZV92ZWN0b3IsCiAgICAgICAgICAgICAgICBmcmFtZXdvcms9IkF6dXJlTUwiLAogICAgICAgICAgICAgICAgYWxnb3JpdGhtPW1vZGVsX2hwX2RpY3QuZ2V0KCJ0cmFpbl9jbGFzc19uYW1lIiksCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgaSA9PSAwOgogICAgICAgICAgICAgICAgIyBUaGlzIGFsc28gbG9ncyB0aGUgbW9kZWw6CiAgICAgICAgICAgICAgICBjaGlsZC5tYXJrX2FzX2Jlc3QoKQoKCmRlZiB0cmFpbigKICAgICMgTWxSdW4KICAgIGNvbnRleHQ6IE1MQ2xpZW50Q3R4LAogICAgZGF0YXNldDogRGF0YUl0ZW0sCiAgICAjIEluaXQgZXhwZXJpbWVudCBhbmQgY29tcHV0ZQogICAgZXhwZXJpbWVudF9uYW1lOiBzdHIgPSAiIiwKICAgIGNwdV9jbHVzdGVyX25hbWU6IHN0ciA9ICIiLAogICAgdm1fc2l6ZTogc3RyID0gIlNUQU5EQVJEX0QyX1YyIiwKICAgIG1heF9ub2RlczogaW50ID0gMSwKICAgICMgUmVnaXN0ZXIgZGF0YXNldAogICAgZGF0YXNldF9uYW1lOiBzdHIgPSAiIiwKICAgIGRhdGFzZXRfZGVzY3JpcHRpb246IHN0ciA9ICIiLAogICAgY3JlYXRlX25ld192ZXJzaW9uOiBib29sID0gRmFsc2UsCiAgICBsYWJlbF9jb2x1bW5fbmFtZTogc3RyID0gIiIsCiAgICAjIFN1Ym1pdCB0cmFpbmluZyBqb2IKICAgIHJlZ2lzdGVyX21vZGVsX25hbWU6IHN0ciA9ICIiLAogICAgc2F2ZV9uX21vZGVsczogaW50ID0gMSwKICAgIGxvZ19henVyZTogYm9vbCA9IFRydWUsCiAgICBhdXRvbWxfc2V0dGluZ3M6IHN0ciA9IE5vbmUsCikgLT4gTm9uZToKICAgICIiIgogICAgV2hvbGUgdHJhaW5pbmcgZmxvdyBmb3IgQXp1cmUgQXV0b01MLiBSZWdpc3RlcnMgZGF0YXNldC9mZWF0dXJlIHZlY3RvciwKICAgIHN1Ym1pdHMgdHJhaW5pbmcgam9iIHRvIEF6dXJlIEF1dG9NTCwgYW5kIGRvd25sb2FkcyB0cmFpbmVkIG1vZGVsCiAgICB3aGVuIGNvbXBsZXRlZC4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgTUxSdW4gY29udGV4dC4KCiAgICA6cGFyYW0gZGF0YXNldDogICAgICAgICAgICAgTUxSdW4gRmVhdHVyZVZlY3RvciBvciBkYXRhc2V0IFVSSSB0byB1cGxvYWQuIFdpbGwgZHJvcAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZGV4IGJlZm9yZSB1cGxvYWRpbmcgd2hlbiBpdCBpcyBhIEZlYXR1cmVWZWN0b3IuCgogICAgOnBhcmFtIGV4cGVyaW1lbnRfbmFtZTogICAgIE5hbWUgb2YgZXhwZXJpbWVudCB0byBjcmVhdGUgaW4gQXp1cmUgTUwuCiAgICA6cGFyYW0gY3B1X2NsdXN0ZXJfbmFtZTogICAgTmFtZSBvZiBBenVyZSBNTCBjb21wdXRlIHRhcmdldC4gQ3JlYXRlZCBpZiBkb2VzIG5vdCBleGlzdC4KICAgIDpwYXJhbSB2bV9zaXplOiAgICAgICAgICAgICBBenVyZSBtYWNoaW5lIHR5cGUgZm9yIGNvbXB1dGUgdGFyZ2V0LgogICAgOnBhcmFtIG1heF9ub2RlczogICAgICAgICAgIE1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29tcHV0ZSB0YXJnZXRzLgoKICAgIDpwYXJhbSBkYXRhc2V0X25hbWU6ICAgICAgICBOYW1lIG9mIEF6dXJlIGRhdGFzZXQgdG8gcmVnaXN0ZXIuCiAgICA6cGFyYW0gZGF0YXNldF9kZXNjcmlwdGlvbjogRGVzY3JpcHRpb24gb2YgQXp1cmUgZGF0YXNldCB0byByZWdpc3Rlci4KCiAgICA6cGFyYW0gY3JlYXRlX25ld192ZXJzaW9uOiAgUmVnaXN0ZXIgQXp1cmUgZGF0YXNldCBhcyBuZXcgdmVyc2lvbi4gTXVzdCBiZSB1c2VkIHdoZW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RpZnlpbmcgZGF0YXNldCBzY2hlbWEuCiAgICA6cGFyYW0gbGFiZWxfY29sdW1uX25hbWU6ICAgVGFyZ2V0IGNvbHVtbiBpbiBkYXRhc2V0LgoKICAgIDpwYXJhbSByZWdpc3Rlcl9tb2RlbF9uYW1lOiBOYW1lIG9mIG1vZGVsIHRvIHJlZ2lzdGVyIGluIEF6dXJlLgogICAgOnBhcmFtIHNhdmVfbl9tb2RlbHM6ICAgICAgIEhvdyBtYW55IG9mIHRoZSB0b3AgcGVyZm9ybWluZyBtb2RlbHMgdG8gbG9nLgogICAgOnBhcmFtIGxvZ19henVyZTogICAgICAgICAgIERpc3BsYXlpbmcgQXp1cmUgbG9ncy4KICAgIDpwYXJhbSBhdXRvbWxfc2V0dGluZ3M6ICAgICBKU09OIHN0cmluZyBvZiBhbGwgQXp1cmUgQXV0b01MIHNldHRpbmdzLgogICAgIiIiCiAgICBpZiBub3QgYXV0b21sX3NldHRpbmdzOgogICAgICAgIGF1dG9tbF9zZXR0aW5ncyA9IHsKICAgICAgICAgICAgInRhc2siOiAiY2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAiZGVidWdfbG9nIjogImF1dG9tbF9lcnJvcnMubG9nIiwKICAgICAgICAgICAgIyAiZXhwZXJpbWVudF9leGl0X3Njb3JlIjogMC45LAogICAgICAgICAgICAiZW5hYmxlX2Vhcmx5X3N0b3BwaW5nIjogRmFsc2UsCiAgICAgICAgICAgICJhbGxvd2VkX21vZGVscyI6IFsiTG9naXN0aWNSZWdyZXNzaW9uIiwgIlNHRCIsICJTVk0iXSwKICAgICAgICAgICAgIml0ZXJhdGlvbnMiOiAzLAogICAgICAgICAgICAiaXRlcmF0aW9uX3RpbWVvdXRfbWludXRlcyI6IDIsCiAgICAgICAgICAgICJtYXhfY29uY3VycmVudF9pdGVyYXRpb25zIjogMiwKICAgICAgICAgICAgIm1heF9jb3Jlc19wZXJfaXRlcmF0aW9uIjogLTEsCiAgICAgICAgICAgICJuX2Nyb3NzX3ZhbGlkYXRpb25zIjogNSwKICAgICAgICAgICAgInByaW1hcnlfbWV0cmljIjogImFjY3VyYWN5IiwKICAgICAgICAgICAgImZlYXR1cml6YXRpb24iOiAib2ZmIiwKICAgICAgICAgICAgIm1vZGVsX2V4cGxhaW5hYmlsaXR5IjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfdm90aW5nX2Vuc2VtYmxlIjogRmFsc2UsCiAgICAgICAgICAgICJlbmFibGVfc3RhY2tfZW5zZW1ibGUiOiBGYWxzZSwKICAgICAgICB9CgogICAgIyBJbml0IGV4cGVyaW1lbnQgYW5kIGNvbXB1dGUKICAgIHdvcmtzcGFjZSwgZXhwZXJpbWVudCA9IF9pbml0X2V4cGVyaW1lbnQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LCBleHBlcmltZW50X25hbWU9ZXhwZXJpbWVudF9uYW1lCiAgICApCgogICAgY29tcHV0ZV90YXJnZXQgPSBpbml0X2NvbXB1dGUoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGNwdV9jbHVzdGVyX25hbWU9Y3B1X2NsdXN0ZXJfbmFtZSwKICAgICAgICB2bV9zaXplPXZtX3NpemUsCiAgICAgICAgbWF4X25vZGVzPW1heF9ub2RlcywKICAgICkKCiAgICAjIFJlZ2lzdGVyIGRhdGFzZXQKICAgIHJlZ2lzdGVyX2RhdGFzZXQoCiAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgIGRhdGFzZXRfbmFtZT1kYXRhc2V0X25hbWUsCiAgICAgICAgZGF0YXNldF9kZXNjcmlwdGlvbj1kYXRhc2V0X2Rlc2NyaXB0aW9uLAogICAgICAgIGRhdGE9ZGF0YXNldCwKICAgICAgICBjcmVhdGVfbmV3X3ZlcnNpb249Y3JlYXRlX25ld192ZXJzaW9uLAogICAgKQoKICAgICMgU3VibWl0IHRyYWluaW5nIGpvYgogICAgc3VibWl0X3RyYWluaW5nX2pvYigKICAgICAgICBjb250ZXh0LAogICAgICAgIGV4cGVyaW1lbnQ9ZXhwZXJpbWVudCwKICAgICAgICBjb21wdXRlX3RhcmdldD1jb21wdXRlX3RhcmdldCwKICAgICAgICByZWdpc3Rlcl9tb2RlbF9uYW1lPXJlZ2lzdGVyX21vZGVsX25hbWUsCiAgICAgICAgcmVnaXN0ZXJlZF9kYXRhc2V0X25hbWU9ZGF0YXNldF9uYW1lLAogICAgICAgIGxhYmVsX2NvbHVtbl9uYW1lPWxhYmVsX2NvbHVtbl9uYW1lLAogICAgICAgIGF1dG9tbF9zZXR0aW5ncz1hdXRvbWxfc2V0dGluZ3MsCiAgICAgICAgdHJhaW5pbmdfc2V0PWRhdGFzZXQsCiAgICAgICAgc2hvd19vdXRwdXQ9bG9nX2F6dXJlLAogICAgICAgIHNhdmVfbl9tb2RlbHM9c2F2ZV9uX21vZGVscywKICAgICkK
+    commands:
+    - apt-get update && apt-get install -y --no-install-recommends git
+    - apt install -y liblttng-ust0
+    base_image: python:3.9-bullseye
+    origin_filename: ''
+  default_handler: train
+  allow_empty_resources: true
+  disable_auto_mount: false
+  image: ''
   entry_points:
     init_compute:
-      name: init_compute
       doc: 'Initialize Azure ML compute target to run experiment. Checks for
 
         existing compute target and creates new if does not exist.'
+      name: init_compute
+      lineno: 102
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -81,16 +75,17 @@
       outputs:
       - doc: Azure ML Compute Target.
         type: ComputeTarget
-        default: ''
-      lineno: 102
+      has_varargs: false
     register_dataset:
-      name: register_dataset
       doc: 'Register dataset object (can be also an Iguazio FeatureVector) in Azure
         ML.
 
         Uploads parquet file to Azure blob storage and registers
 
         that file as a dataset in Azure ML.'
+      name: register_dataset
+      lineno: 138
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -109,12 +104,12 @@
         doc: Register Azure dataset as new version. Must be used when modifying dataset
           schema.
         default: false
-      outputs:
-      - default: ''
-      lineno: 138
+      has_varargs: false
     download_model:
-      name: download_model
       doc: Download trained model from Azure ML to local filesystem.
+      name: download_model
+      lineno: 217
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -130,11 +125,13 @@
         doc: Target directory to download model.
         default: .
       outputs:
-      - default: ''
-      lineno: 217
+      - type: None
+      has_varargs: false
     upload_model:
-      name: upload_model
       doc: Upload pre-trained model from local filesystem to Azure ML.
+      name: upload_model
+      lineno: 238
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -154,13 +151,15 @@
         doc: KV pairs of model tags.
         default: null
       outputs:
-      - default: ''
-      lineno: 238
+      - type: None
+      has_varargs: false
     submit_training_job:
-      name: submit_training_job
       doc: 'Submit training job to Azure AutoML and download trained model
 
         when completed. Uses previously registered dataset for training.'
+      name: submit_training_job
+      lineno: 352
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -196,15 +195,17 @@
         doc: Displaying Azure logs.
         default: true
       outputs:
-      - default: ''
-      lineno: 352
+      - type: None
+      has_varargs: false
     train:
-      name: train
       doc: 'Whole training flow for Azure AutoML. Registers dataset/feature vector,
 
         submits training job to Azure AutoML, and downloads trained model
 
         when completed.'
+      name: train
+      lineno: 469
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
@@ -263,21 +264,17 @@
         doc: JSON string of all Azure AutoML settings.
         default: null
       outputs:
-      - default: ''
-      lineno: 469
+      - type: None
+      has_varargs: false
   description: Azure AutoML integration in MLRun, including utils functions for training
     models on Azure AutoML platfrom.
-  default_handler: train
-  disable_auto_mount: false
-  allow_empty_resources: true
-  clone_target_dir: ''
-  env: []
-  priority_class_name: ''
-  preemption_mode: prevent
-  affinity: null
-  tolerations: null
-  security_context: {}
-verbose: false
+kind: job
+metadata:
+  categories:
+  - model-serving
+  - utils
+  tag: ''
+  name: azureml-utils
 
         
     
diff --git a/functions/master/azureml_utils/latest/static/item.html b/functions/master/azureml_utils/latest/static/item.html index 5eb0f63b..60bd9b2e 100644 --- a/functions/master/azureml_utils/latest/static/item.html +++ b/functions/master/azureml_utils/latest/static/item.html @@ -30,8 +30,8 @@ apiVersion: v1 categories: -- machine-learning -- model-training +- model-serving +- utils description: Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom. doc: '' @@ -43,7 +43,7 @@ author: yonish maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: azureml_utils platformVersion: 3.5.3 spec: @@ -64,7 +64,7 @@ - azureml-train-automl-client==1.54.0.post1 - plotly~=5.4 url: '' -version: 1.3.0 +version: 1.4.0 test_valid: True diff --git a/functions/master/batch_inference/1.8.0/src/batch_inference.ipynb b/functions/master/batch_inference/1.8.0/src/batch_inference.ipynb new file mode 100644 index 00000000..d949bf23 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/batch_inference.ipynb @@ -0,0 +1,1789 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Batch Inference\n", + "\n", + "A function for inferring given input through a given model while producing a **Result Set** and performing **Data Drift Analysis**.\n", + "\n", + "In this notebook we will go over the function's docs and outputs and see an end-to-end example of running it.\n", + "\n", + "1. [Documentation](#chapter1)\n", + "2. [Results Prediction](#chapter2)\n", + "3. [Data Drift Analysis](#chapter3)\n", + "4. [End-to-end Demo](#chapter4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "\n", + "## 1. Documentation\n", + "\n", + "Perform a prediction on a given dataset with the given model. Can perform drift analysis between the sample set statistics stored in the model to the current input data. The drift rule is the value per-feature mean of the TVD and Hellinger scores according to the thresholds configures here." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 1.1. Parameters:\n", + "* **context**: `mlrun.MLClientCtx`\n", + "\n", + " An MLRun context.\n", + " \n", + "* **model**: `str`\n", + " \n", + " The model Store path, a logged model URI.\n", + " \n", + "* **dataset**: `Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]`\n", + " \n", + " The dataset to infer through the model.\n", + " * Can be passed in `inputs` as either a Dataset artifact / Feature vector URI.\n", + " * Or, in `parameters` as a list, dictionary or numpy array.\n", + " \n", + " \n", + "* **drop_columns**: `Union[str, List[str], int, List[int]]` = `None`\n", + " \n", + " A string / integer or a list of strings / integers that represent the column names / indices to drop. When the dataset is a list or a numpy array this parameter must be represented by integers.\n", + " \n", + "* **label_columns**: `Union[str, List[str]]` = `None`\n", + " \n", + " The target label(s) of the column(s) in the dataset. These names will be used as the column names for the predictions. The label column can be accessed from the model object, or the feature vector provided if available. The default name is `\"predicted_label_i\"` for the `i` column.\n", + "\n", + "* **feature_columns**: `Union[str, List[str]]` = `None`\n", + " \n", + " List of feature columns that will be used to build the dataframe when dataset is\n", + " from type list or numpy array.\n", + "\n", + "* **log_result_set**: `str` = `True`\n", + " \n", + " Whether to log the result set - a DataFrame of the given inputs concatenated with the predictions. Defaulted to `True`.\n", + "\n", + "* **result_set_name**: `str` = `\"prediction\"`\n", + " \n", + " The db key to set name of the prediction result and the filename. Defaulted to `\"prediction\"`.\n", + "\n", + "* **batch_id**: `str` = `None`\n", + "\n", + " The ID of the given batch (inference dataset). If `None`, it will be generated. Will be logged as a result of the run.\n", + "\n", + "* **perform_drift_analysis**: `bool` = `None`\n", + " \n", + " Whether to perform drift analysis between the sample set of the model object to the dataset given. By default, `None`, which means it will perform drift analysis if the model has a sample set statistics.\n", + "\n", + "\n", + "* **sample_set**: `Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]`\n", + " \n", + " A sample dataset to give to compare the inputs in the drift analysis. The default chosen sample set will always be the one who is set in the model artifact itself.\n", + " * Can be passed in `inputs` as either a Dataset artifact / Feature vector URI.\n", + " * Or, in `parameters` as a list, dictionary or numpy array.\n", + "\n", + "\n", + "* **drift_threshold**: `float` = `0.7`\n", + " \n", + " The threshold of which to mark drifts. Defaulted to 0.7.\n", + "\n", + "* **possible_drift_threshold**: `float` = `0.5`\n", + " \n", + " The threshold of which to mark possible drifts. Defaulted to 0.5.\n", + "\n", + "* **inf_capping**: `float` = `10.0`\n", + " \n", + " The value to set for when it reached infinity. Defaulted to 10.0.\n", + "\n", + "* **artifacts_tag**: `str` = `\"\"`\n", + " \n", + " Tag to use for all the artifacts resulted from the function. Defaulted to no tag." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 1.2. Outputs\n", + "\n", + "The outputs are split to two actions the functions can perform:\n", + "* [**Results Prediction**](#chapter2) - Will log:\n", + " * A dataset artifact named by the `result_set_name` parameter.\n", + " * A `str` result named `\"batch_id\"` of the given / generated batch ID.\n", + "\n", + "* [**Data Drift Analysis**](#chapter3) - Will log:\n", + " * A `plotly` artifact named `\"data_drift_table\"` with a visualization of the drifts results and histograms.\n", + " * A json artifact named `\"features_drift_results\"` with all the features metric values.\n", + " * A `bool` result named `\"drift_status\"` of the overall drift status (`True` if there was a drift and `False` otherwise).\n", + " * A `float` result named `\"drift_score\"` of the overall drift metric score.\n", + "\n", + "For more details, see the next chapters." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "\n", + "## 2. Results Prediction\n", + "\n", + "The result set is a concatenated dataset of the inputs ($X$) provided and the predictions ($Y$) yielded by the model, so it will be $X | Y$.\n", + "\n", + "For example, if the `dataset` given as inputs was:\n", + "\n", + "| x1 | x2 | x3 | x4 | x5 |\n", + "|-----|-----|-----|-----|-----|\n", + "| ... | ... | ... | ... | ... |\n", + "| ... | ... | ... | ... | ... |\n", + "| ... | ... | ... | ... | ... |\n", + "\n", + "And the outputs yielded by the model's prediction was:\n", + "\n", + "| y1 | y2 |\n", + "|-----|-----|\n", + "| ... | ... |\n", + "| ... | ... |\n", + "| ... | ... |\n", + "\n", + "Then the result set will be:\n", + "\n", + "| x1 | x2 | x3 | x4 | x5 | y1 | y2 |\n", + "|-----|-----|-----|-----|-----|-----|-----|\n", + "| ... | ... | ... | ... | ... | ... | ... |\n", + "| ... | ... | ... | ... | ... | ... | ... |\n", + "| ... | ... | ... | ... | ... | ... | ... |\n", + "\n", + "In case the parameter `log_result_set` is `True`, the outputs of the results prediction will be:\n", + "* The result set as described above.\n", + "* The batch ID result - `batch_id`: `str` - a hashing result that is given by the user or generated randomly in case it was not provided to represent the batch that was being inferred.\n", + "\n", + " ```python\n", + " {\n", + " \"batch_id\": \"884a0cb00d8ae16d132dd8259aac29aa78f50a9245d0e4bd58cfbf77\",\n", + " }\n", + " ```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "\n", + "## 3. Data Drift Analysis\n", + "\n", + "The data drift analysis is done per feature using two distance measure metrics for probability distributions.\n", + "\n", + "Let us mark our sample set as $S$ and our inputs as $I$. We will look at one feature $x$ out of $n$ features. Assuming the histograms of feature $x$ is split into 20 bins: $b_1,b_2,...,b_{20}$, we will match the feature $x$ histogram of the inputs $I$ ($x_I$) into the same bins (meaning to $x_S$) and compare their distributions using:\n", + "\n", + "* Total Variance Distance: $TVD(x_S,x_I) = \\frac{1}{2}\\sum_{b_1}^{b_{20}} {|x_S - x_I|}$\n", + "* Hellinger Distance: $H(x_S,x_I) = \\sqrt{1-{\\sum_{b_1}^{b_{20}}\\sqrt{x_S \\cdot x_I}}}$\n", + "\n", + "Our **rule** then is calculating for each $x\\in S: \\frac{H(x_S,x_I)+TVD(x_S,x_I)}{2} < $ given thresholds.\n", + "\n", + "In case the parameter `perform_drift_analysis` is `True`, the outputs of the analysis will be:\n", + "* **Drift table plot** - The results are presented in a `plotly` table artifact named `\"drift_table_plot\"` that shows each feature's statistics and its TVD, Hellinger and KLD (Kullback–Leibler divergence) results as follows:\n", + "\n", + "| | Count | | Mean | | Std | | Min | | Max | | Tvd | Hellinger | Kld | Histograms |\n", + "| ------ | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | --- | --------- | --- |------------|\n", + "| | **Sample** | **Input** | **Sample** | **Input** | **Sample** | **Input** | **Sample** | **Input** | **Sample** | **Input** | | | | |\n", + "| **x1** | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |\n", + "| **x2** | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |\n", + "| **x3** | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |\n", + "\n", + "* **Features drift results** - A rule metric per feature dictionary is saved in a json file named `\"features_drift_results\"` where each key is a feature and its value is the feature's metric value: `Dict[str, float]`\n", + "\n", + " ```python\n", + " {\n", + " \"x1\": 0.12,\n", + " \"x2\": 0.345,\n", + " \"x3\": 0.00678,\n", + " ...\n", + " }\n", + " ```\n", + "\n", + "* In addition, two results are being added to summarize the drift analysis:\n", + "\n", + " * `drift_status`: `bool` - A boolean value indicating whether a drift was found.\n", + " * `drift_metric`: `float` - The mean of all the features drift metric value (the rule above):\n", + " for $n$ features and metric rule $M(x_S,x_I)=\\frac{H(x_S,x_I)+TVD(x_S,x_I)}{2}$, `drift_metric` $=\\frac{1}{n}\\sum_{x\\in S}M(x_S,x_I)$\n", + "\n", + " ```python\n", + " {\n", + " \"drift_status\": True,\n", + " \"drift_metric\": 0.81234\n", + " }\n", + " ```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "\n", + "## 4. End-to-end Demo\n", + "\n", + "We will see an end-to-end example that follows the steps below:\n", + "1. Generate data.\n", + "2. Train a model.\n", + "3. Infer data through the model using `batch_predict` and review the outputs." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 4.1. Code review\n", + "\n", + "We are using a very simple example of training a decision tree on a binary classification problem. For that we wrote two functions:\n", + "* `generate_data` - Generate a binary classification data. The data will be split into a *training set* and *data for prediction*. The data for prediction will be drifted in half of its features to showcase the plot later on.\n", + "* `train` - Train a decision tree classifier on a given data." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# mlrun: start-code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# upload environment variables from env file if exists\n", + "import os,mlrun\n", + "\n", + "# Specify path\n", + "path = \"/tmp/examples_ci.env\"\n", + "\n", + "if os.path.exists(path):\n", + " env_dict = mlrun.set_env_from_file(path, return_dict=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from sklearn.datasets import make_classification\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "\n", + "import mlrun\n", + "from mlrun.frameworks.sklearn import apply_mlrun\n", + "\n", + "\n", + "@mlrun.handler(outputs=[\"training_set\", \"prediction_set\"])\n", + "def generate_data(n_samples: int = 5000, n_features: int = 20):\n", + " # Generate a classification data:\n", + " x, y = make_classification(\n", + " n_samples=n_samples, n_features=n_features, n_classes=2\n", + " )\n", + "\n", + " # Split the data into a training set and a prediction set:\n", + " x_train, x_prediction = x[: n_samples // 2], x[n_samples // 2 :]\n", + " y_train = y[: n_samples // 2]\n", + " \n", + " # Randomly drift some features:\n", + " x_prediction += (\n", + " np.random.uniform(low=2, high=4, size=x_train.shape) * \n", + " np.random.randint(low=0, high=2, size=x_train.shape[1], dtype=int)\n", + " )\n", + " \n", + " # Initialize dataframes:\n", + " features = [f\"feature_{i}\" for i in range(n_features)]\n", + " training_set = pd.DataFrame(data=x_train, columns=features)\n", + " training_set.insert(\n", + " loc=n_features, column=\"label\", value=y_train, allow_duplicates=True\n", + " )\n", + " prediction_set = pd.DataFrame(data=x_prediction, columns=features)\n", + "\n", + " return training_set, prediction_set\n", + "\n", + "\n", + "@mlrun.handler()\n", + "def train(training_set: pd.DataFrame):\n", + " # Get the data into x, y:\n", + " labels = pd.DataFrame(training_set[\"label\"])\n", + " training_set.drop(columns=[\"label\"], inplace=True)\n", + "\n", + " # Initialize a model:\n", + " model = DecisionTreeClassifier()\n", + "\n", + " # Apply MLRun:\n", + " apply_mlrun(model=model, model_name=\"model\")\n", + "\n", + " # Train:\n", + " model.fit(training_set, labels)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# mlrun: end-code" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 4.2. Run the Example with MLRun\n", + "\n", + "First, we will prepare our MLRun functions:\n", + "1. We will use `mlrun.code_to_function` to turn this demo notebook into an MLRun function we can run.\n", + "2. We will use `mlrun.import_function` to import the `batch_predict` function ." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Create an MLRun function to run the notebook:\n", + "demo_function = mlrun.code_to_function(name=\"batch_inference_demo\", kind=\"job\")\n", + "\n", + "# Import the `batch_predict` function from the marketplace:\n", + "batch_inference_function = mlrun.import_function(\"hub://batch_inference\")\n", + "\n", + "# Set the desired artifact path:\n", + "artifact_path = \"./\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now, we will follow the demo steps as discussed above:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-09-13 09:54:59,693 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI\n", + "> 2022-09-13 09:54:59,694 [info] starting run batch-predict-demo-generate_data uid=a5b1ca0a37d946e892b9305b9af833c3 DB=http://mlrun-api:8080\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:54:59completedbatch-predict-demo-generate_data
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
training_set
prediction_set
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-09-13 09:55:06,462 [info] run executed, status=completed\n", + "> 2022-09-13 09:55:06,464 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI\n", + "> 2022-09-13 09:55:06,464 [info] starting run batch-predict-demo-train uid=384b36e84c4e4f91900e49e1f24ff1a6 DB=http://mlrun-api:8080\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:55:06completedbatch-predict-demo-train
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
training_set
model
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-09-13 09:55:07,367 [info] run executed, status=completed\n", + "> 2022-09-13 09:55:07,370 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI\n", + "> 2022-09-13 09:55:07,370 [info] starting run batch-predict-predict uid=cf88e39d59704912a5ee41ceb539cd05 DB=http://mlrun-api:8080\n", + "> 2022-09-13 09:55:07,703 [info] Loading model...'\n", + "> 2022-09-13 09:55:07,753 [info] Calculating prediction...\n", + "> 2022-09-13 09:55:07,757 [info] Logging result set (x | prediction)...\n", + "> 2022-09-13 09:55:07,952 [info] Performing drift analysis...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "divide by zero encountered in log\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:55:07completedbatch-predict-predict
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
dataset
model=store://artifacts/default/model:384b36e84c4e4f91900e49e1f24ff1a6
label_columns=label
drift_status=False
drift_metric=0.3880999515903545
prediction
drift_table_plot
features_drift_results
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2022-09-13 09:55:10,078 [info] run executed, status=completed\n" + ] + } + ], + "source": [ + "# 1. Generate data:\n", + "generate_data_run = demo_function.run(\n", + " handler=\"generate_data\",\n", + " artifact_path=artifact_path,\n", + " local=True,\n", + ")\n", + "\n", + "# 2. Train a model:\n", + "train_run = demo_function.run(\n", + " handler=\"train\",\n", + " artifact_path=artifact_path,\n", + " inputs={\"training_set\": generate_data_run.outputs[\"training_set\"]},\n", + " local=True,\n", + ")\n", + "\n", + "# 3. Perform batch prediction:\n", + "batch_inference_run = batch_inference_function.run(\n", + " handler=\"infer\",\n", + " artifact_path=artifact_path,\n", + " inputs={\"dataset\": generate_data_run.outputs[\"prediction_set\"]},\n", + " params={\n", + " \"model\": train_run.outputs[\"model\"],\n", + " \"label_columns\": \"label\",\n", + " },\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 4.3. Review Outputs\n", + "\n", + "We will review the outputs as explained in the notebook above." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### 4.3.1. Results Prediction\n", + "\n", + "First we will showcase the **Result Set**. As we didn't send any name, it's default name will be `\"prediction\"`:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
feature_0feature_1feature_2feature_3feature_4feature_5feature_6feature_7feature_8feature_9...feature_11feature_12feature_13feature_14feature_15feature_16feature_17feature_18feature_19label
06.3191113.2082100.793499-0.6132522.6347661.2083524.7187353.557495-2.1163110.370145...1.9629010.5813214.8445321.4087370.9649872.1114563.134610-1.7279370.3357410
13.1383781.6336610.1445570.6870032.4042791.4059903.2358921.8044262.0199801.719908...-1.4868090.6482283.0397971.8067661.2016922.4759741.4485590.9592661.1586511
22.3530264.6428790.9520970.6059774.051640-0.1575841.2187432.4647381.706084-0.250366...0.5599680.9793782.4117033.7468302.2521553.4061023.263166-0.236510-0.3131611
31.6172024.5683322.9379612.5011663.9525410.6717493.7745944.042543-2.173079-0.983443...-0.8390100.9536983.0335511.0068912.3985635.0473825.2912601.3055840.8439511
43.3442914.5383571.032059-0.0479313.1184380.4038124.4726151.840558-0.7147750.287726...1.598060-0.8055084.7420324.6087921.6177174.5148953.648923-1.3440240.6105340
..................................................................
24952.3193012.9969411.3379340.8056492.3036560.2030695.5755593.4377900.7097770.392013...-0.114619-1.4697974.5381261.2824985.6861332.8269732.445658-0.1457800.3378030
24962.9206782.1449832.153517-0.5272952.6120401.1137042.4387613.2844251.0938940.921599...-1.5868520.4098384.0947632.6366543.3334143.2511061.1329761.072658-1.2401861
24974.2566982.135673-0.1144910.3299803.935633-0.7779582.5436432.195111-0.926822-0.251254...-0.9528890.6878202.2680435.0774542.2482593.4697042.2629000.687038-0.6140661
24984.7380302.390842-0.9723291.4714612.904280-2.0790882.5706042.325262-1.602976-0.806244...0.5543990.0274934.1457283.7828024.2020063.2727090.867462-1.0200292.0133010
24992.0478312.1538130.3924840.2490103.8469100.3008463.0059972.799457-0.304962-0.990622...-0.2634730.1100912.9954112.5828434.5995353.2190911.592652-0.074851-0.6177691
\n", + "

2500 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " feature_0 feature_1 feature_2 feature_3 feature_4 feature_5 \\\n", + "0 6.319111 3.208210 0.793499 -0.613252 2.634766 1.208352 \n", + "1 3.138378 1.633661 0.144557 0.687003 2.404279 1.405990 \n", + "2 2.353026 4.642879 0.952097 0.605977 4.051640 -0.157584 \n", + "3 1.617202 4.568332 2.937961 2.501166 3.952541 0.671749 \n", + "4 3.344291 4.538357 1.032059 -0.047931 3.118438 0.403812 \n", + "... ... ... ... ... ... ... \n", + "2495 2.319301 2.996941 1.337934 0.805649 2.303656 0.203069 \n", + "2496 2.920678 2.144983 2.153517 -0.527295 2.612040 1.113704 \n", + "2497 4.256698 2.135673 -0.114491 0.329980 3.935633 -0.777958 \n", + "2498 4.738030 2.390842 -0.972329 1.471461 2.904280 -2.079088 \n", + "2499 2.047831 2.153813 0.392484 0.249010 3.846910 0.300846 \n", + "\n", + " feature_6 feature_7 feature_8 feature_9 ... feature_11 feature_12 \\\n", + "0 4.718735 3.557495 -2.116311 0.370145 ... 1.962901 0.581321 \n", + "1 3.235892 1.804426 2.019980 1.719908 ... -1.486809 0.648228 \n", + "2 1.218743 2.464738 1.706084 -0.250366 ... 0.559968 0.979378 \n", + "3 3.774594 4.042543 -2.173079 -0.983443 ... -0.839010 0.953698 \n", + "4 4.472615 1.840558 -0.714775 0.287726 ... 1.598060 -0.805508 \n", + "... ... ... ... ... ... ... ... \n", + "2495 5.575559 3.437790 0.709777 0.392013 ... -0.114619 -1.469797 \n", + "2496 2.438761 3.284425 1.093894 0.921599 ... -1.586852 0.409838 \n", + "2497 2.543643 2.195111 -0.926822 -0.251254 ... -0.952889 0.687820 \n", + "2498 2.570604 2.325262 -1.602976 -0.806244 ... 0.554399 0.027493 \n", + "2499 3.005997 2.799457 -0.304962 -0.990622 ... -0.263473 0.110091 \n", + "\n", + " feature_13 feature_14 feature_15 feature_16 feature_17 feature_18 \\\n", + "0 4.844532 1.408737 0.964987 2.111456 3.134610 -1.727937 \n", + "1 3.039797 1.806766 1.201692 2.475974 1.448559 0.959266 \n", + "2 2.411703 3.746830 2.252155 3.406102 3.263166 -0.236510 \n", + "3 3.033551 1.006891 2.398563 5.047382 5.291260 1.305584 \n", + "4 4.742032 4.608792 1.617717 4.514895 3.648923 -1.344024 \n", + "... ... ... ... ... ... ... \n", + "2495 4.538126 1.282498 5.686133 2.826973 2.445658 -0.145780 \n", + "2496 4.094763 2.636654 3.333414 3.251106 1.132976 1.072658 \n", + "2497 2.268043 5.077454 2.248259 3.469704 2.262900 0.687038 \n", + "2498 4.145728 3.782802 4.202006 3.272709 0.867462 -1.020029 \n", + "2499 2.995411 2.582843 4.599535 3.219091 1.592652 -0.074851 \n", + "\n", + " feature_19 label \n", + "0 0.335741 0 \n", + "1 1.158651 1 \n", + "2 -0.313161 1 \n", + "3 0.843951 1 \n", + "4 0.610534 0 \n", + "... ... ... \n", + "2495 0.337803 0 \n", + "2496 -1.240186 1 \n", + "2497 -0.614066 1 \n", + "2498 2.013301 0 \n", + "2499 -0.617769 1 \n", + "\n", + "[2500 rows x 21 columns]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batch_inference_run.artifact(\"prediction\").as_df()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### 4.3.2. Data Drift Analysis\n", + "\n", + "Second we will review the data drift table plot and the drift results:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
\n", + "
\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "batch_inference_run.artifact(\"drift_table_plot\").show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'drift_status': False, 'drift_metric': 0.3880999515903545}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "batch_inference_run.status.results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/functions/master/batch_inference/1.8.0/src/batch_inference.py b/functions/master/batch_inference/1.8.0/src/batch_inference.py new file mode 100644 index 00000000..844fdf39 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/batch_inference.py @@ -0,0 +1,445 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import hashlib +import json +from datetime import datetime +from typing import Any, Dict, List, Tuple, Union +import semver + +import mlrun +if semver.compare(mlrun.__version__, "1.5.0") >= 0: + raise mlrun.errors.MLRunNotFoundError( + f"When using `mlrun` version >=1.5.0, please use " + f"batch inference `v2` function ('hub://batch_inference_v2')." + ) + +import mlrun.datastore +import mlrun.utils +import numpy as np +import pandas as pd +from mlrun import feature_store as fs +from mlrun.artifacts import Artifact +from mlrun.data_types.infer import InferOptions, get_df_stats +from mlrun.frameworks.auto_mlrun import AutoMLRun +from mlrun.model_monitoring.features_drift_table import FeaturesDriftTablePlot +from mlrun.model_monitoring.model_monitoring_batch import ( + VirtualDrift, + calculate_inputs_statistics, +) + +# A union of all supported dataset types: +DatasetType = Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray] + + +def _read_dataset_as_dataframe( + dataset: DatasetType, + feature_columns: Union[str, List[str]] = None, + label_columns: Union[str, List[str]] = None, + drop_columns: Union[str, List[str], int, List[int]] = None, +) -> Tuple[pd.DataFrame, List[str]]: + """ + Parse the given dataset into a DataFrame and drop the columns accordingly. In addition, the label columns will be + parsed and validated as well. + + :param dataset: A dataset that will be converted into a DataFrame. + Can be either a list of lists, dict, URI or a FeatureVector. + :param feature_columns: List of feature columns that will be used to build the dataframe when dataset is from + type list or numpy array. + :param label_columns: The target label(s) of the column(s) in the dataset. for Regression or + Classification tasks. + :param drop_columns: ``str`` / ``int`` or a list of ``str`` / ``int`` that represent the column names / indices + to drop. + + :returns: A tuple of: + [0] = The parsed dataset as a DataFrame + [1] = Label columns. + + raises MLRunInvalidArgumentError: If the `drop_columns` are not matching the dataset or unsupported dataset type. + """ + # Turn the `drop labels` into a list if given: + if drop_columns is not None: + if not isinstance(drop_columns, list): + drop_columns = [drop_columns] + + # Check if the dataset is in fact a Feature Vector: + if isinstance(dataset, fs.FeatureVector): + # Try to get the label columns if not provided: + if label_columns is None: + label_columns = dataset.status.label_column + # Get the features and parse to DataFrame: + dataset = fs.get_offline_features( + dataset.uri, drop_columns=drop_columns + ).to_dataframe() + + elif isinstance(dataset, (list, np.ndarray)): + if not feature_columns: + raise mlrun.errors.MLRunInvalidArgumentError( + "Feature columns list must be provided when dataset input as from type list or numpy array" + ) + # Parse the list / numpy array into a DataFrame: + dataset = pd.DataFrame(dataset, columns=feature_columns) + # Validate the `drop_columns` is given as integers: + if drop_columns and not all(isinstance(col, int) for col in drop_columns): + raise mlrun.errors.MLRunInvalidArgumentError( + "`drop_columns` must be an integer / list of integers if provided as a list." + ) + elif isinstance(dataset, mlrun.DataItem): + # Turn the DataITem to DataFrame: + dataset = dataset.as_df() + else: + # Parse the object (should be a pd.DataFrame / pd.Series, dictionary) into a DataFrame: + try: + dataset = pd.DataFrame(dataset) + except ValueError as e: + raise mlrun.errors.MLRunInvalidArgumentError( + f"Could not parse the given dataset of type {type(dataset)} into a pandas DataFrame. " + f"Received the following error: {e}" + ) + # Drop columns if needed: + if drop_columns: + dataset.drop(drop_columns, axis=1, inplace=True) + + # Turn the `label_columns` into a list by default: + if label_columns is None: + label_columns = [] + elif isinstance(label_columns, (str, int)): + label_columns = [label_columns] + return dataset, label_columns + + +def _prepare_result_set( + x: pd.DataFrame, label_columns: List[str], y_pred: np.ndarray +) -> pd.DataFrame: + """ + Set default label column names and validate given names to prepare the result set - a concatenation of the inputs + (x) and the model predictions (y_pred). + + :param x: The inputs. + :param label_columns: A list of strings representing the target column names to add to the predictions. Default name + will be used in case the list is empty (predicted_label_{i}). + :param y_pred: The model predictions on the inputs. + + :returns: The result set. + + raises MLRunInvalidArgumentError: If the labels columns amount do not match the outputs or if one of the label + column already exists in the dataset. + """ + # Prepare default target columns names if not provided: + prediction_columns_amount = 1 if len(y_pred.shape) == 1 else y_pred.shape[1] + if len(label_columns) == 0: + # Add default label column names: + if prediction_columns_amount == 1: + label_columns = ["predicted_label"] + else: + label_columns = [ + f"predicted_label_{i}" for i in range(prediction_columns_amount) + ] + + # Validate the label columns: + if prediction_columns_amount != len(label_columns): + # No equality between provided label column names and outputs amount: + raise mlrun.errors.MLRunInvalidArgumentError( + f"The number of predicted labels: {prediction_columns_amount} " + f"is not equal to the given label columns: {len(label_columns)}" + ) + common_labels = set(label_columns) & set(x.columns.tolist()) + if common_labels: + # Label column exist in the original inputs: + raise mlrun.errors.MLRunInvalidArgumentError( + f"The labels: {common_labels} are already existed in the given dataset." + ) + + return pd.concat( + [x, pd.DataFrame(y_pred, columns=label_columns, index=x.index)], axis=1 + ) + + +def _get_sample_set_statistics( + sample_set: DatasetType = None, model_artifact_feature_stats: dict = None +) -> dict: + """ + Get the sample set statistics either from the given sample set or the statistics logged with the model while + favoring the given sample set. + + :param sample_set: A sample dataset to give to compare the inputs in the drift analysis. + :param model_artifact_feature_stats: The `feature_stats` attribute in the spec of the model artifact, where the + original sample set statistics of the model was used. + + :returns: The sample set statistics. + + raises MLRunInvalidArgumentError: If no sample set or statistics were given. + """ + # Check if a sample set was provided: + if sample_set is None: + # Check if the model was logged with a sample set: + if model_artifact_feature_stats is None: + raise mlrun.errors.MLRunInvalidArgumentError( + "Cannot perform drift analysis as there is no sample set to compare to. The model artifact was not " + "logged with a sample set and `sample_set` was not provided to the function." + ) + # Return the statistics logged with the model: + return model_artifact_feature_stats + + # Turn the DataItem to DataFrame: + if isinstance(sample_set, mlrun.DataItem): + sample_set, _ = _read_dataset_as_dataframe(dataset=sample_set) + + # Return the sample set statistics: + return get_df_stats(df=sample_set, options=InferOptions.Histogram) + + +def _get_drift_result( + tvd: float, + hellinger: float, + threshold: float, +) -> Tuple[bool, float]: + """ + Calculate the drift result by the following equation: (tvd + hellinger) / 2 + + :param tvd: The feature's TVD value. + :param hellinger: The feature's Hellinger value. + :param threshold: The threshold from which the value is considered a drift. + + :returns: A tuple of: + [0] = Boolean value as the drift status. + [1] = The result. + """ + result = (tvd + hellinger) / 2 + if result >= threshold: + return True, result + return False, result + + +def _perform_drift_analysis( + sample_set_statistics: dict, + inputs: pd.DataFrame, + drift_threshold: float, + possible_drift_threshold: float, + inf_capping: float, +) -> Tuple[Artifact, Artifact, dict]: + """ + Perform drift analysis, producing the drift table artifact for logging post prediction. + + :param sample_set_statistics: The statistics of the sample set logged along a model. + :param inputs: Input dataset to perform the drift calculation on. + :param drift_threshold: The threshold of which to mark drifts. + :param possible_drift_threshold: The threshold of which to mark possible drifts. + :param inf_capping: The value to set for when it reached infinity. + + :returns: A tuple of + [0] = An MLRun artifact holding the HTML code of the drift table plot. + [1] = An MLRun artifact holding the metric per feature dictionary. + [2] = Results to log the final analysis outcome. + """ + # Calculate the input's statistics: + inputs_statistics = calculate_inputs_statistics( + sample_set_statistics=sample_set_statistics, + inputs=inputs, + ) + + # Calculate drift: + virtual_drift = VirtualDrift(inf_capping=inf_capping) + metrics = virtual_drift.compute_drift_from_histograms( + feature_stats=sample_set_statistics, + current_stats=inputs_statistics, + ) + drift_results = virtual_drift.check_for_drift_per_feature( + metrics_results_dictionary=metrics, + possible_drift_threshold=possible_drift_threshold, + drift_detected_threshold=drift_threshold, + ) + + # Validate all feature columns named the same between the inputs and sample sets: + sample_features = set( + [ + feature_name + for feature_name, feature_statistics in sample_set_statistics.items() + if isinstance(feature_statistics, dict) + ] + ) + input_features = set(inputs.columns) + if len(sample_features & input_features) != len(input_features): + raise mlrun.errors.MLRunInvalidArgumentError( + f"Not all feature names were matching between the inputs and the sample set provided: " + f"{input_features - sample_features | sample_features - input_features}" + ) + + # Plot: + html_plot = FeaturesDriftTablePlot().produce( + features=list(input_features), + sample_set_statistics=sample_set_statistics, + inputs_statistics=inputs_statistics, + metrics=metrics, + drift_results=drift_results, + ) + + # Prepare metrics per feature dictionary: + metrics_per_feature = { + feature: _get_drift_result( + tvd=metric_dictionary["tvd"], + hellinger=metric_dictionary["hellinger"], + threshold=drift_threshold, + )[1] + for feature, metric_dictionary in metrics.items() + if isinstance(metric_dictionary, dict) + } + + # Calculate the final analysis result: + drift_status, drift_metric = _get_drift_result( + tvd=metrics["tvd_mean"], + hellinger=metrics["hellinger_mean"], + threshold=drift_threshold, + ) + + return ( + Artifact(body=html_plot, format="html", key="drift_table_plot"), + Artifact( + body=json.dumps(metrics_per_feature), + format="json", + key="features_drift_results", + ), + {"drift_status": drift_status, "drift_metric": drift_metric}, + ) + + +def infer( + context: mlrun.MLClientCtx, + model: str, + dataset: DatasetType, + drop_columns: Union[str, List[str], int, List[int]] = None, + label_columns: Union[str, List[str]] = None, + feature_columns: Union[str, List[str]] = None, + log_result_set: bool = True, + result_set_name: str = "prediction", + batch_id: str = None, + perform_drift_analysis: bool = None, + sample_set: DatasetType = None, + drift_threshold: float = 0.7, + possible_drift_threshold: float = 0.5, + inf_capping: float = 10.0, + artifacts_tag: str = "", + **predict_kwargs: Dict[str, Any], +): + """ + Perform a prediction on a given dataset with the given model. Can perform drift analysis between the sample set + statistics stored in the model to the current input data. The drift rule is the value per-feature mean of the TVD + and Hellinger scores according to the thresholds configures here. + + :param context: MLRun context. + :param model: The model Store path. + :param dataset: The dataset to infer through the model. Can be passed in `inputs` as either a + Dataset artifact / Feature vector URI. Or, in `parameters` as a list, dictionary or + numpy array. + :param drop_columns: A string / integer or a list of strings / integers that represent the column names + / indices to drop. When the dataset is a list or a numpy array this parameter must + be represented by integers. + :param label_columns: The target label(s) of the column(s) in the dataset for Regression or + Classification tasks. The label column can be accessed from the model object, or + the feature vector provided if available. + :param feature_columns: List of feature columns that will be used to build the dataframe when dataset is + from type list or numpy array. + :param log_result_set: Whether to log the result set - a DataFrame of the given inputs concatenated with + the predictions. Defaulted to True. + :param result_set_name: The db key to set name of the prediction result and the filename. Defaulted to + 'prediction'. + :param batch_id: The ID of the given batch (inference dataset). If `None`, it will be generated. + Will be logged as a result of the run. + :param perform_drift_analysis: Whether to perform drift analysis between the sample set of the model object to the + dataset given. By default, None, which means it will perform drift analysis if the + model has a sample set statistics. Perform drift analysis will produce a data drift + table artifact. + :param sample_set: A sample dataset to give to compare the inputs in the drift analysis. The default + chosen sample set will always be the one who is set in the model artifact itself. + :param drift_threshold: The threshold of which to mark drifts. Defaulted to 0.7. + :param possible_drift_threshold: The threshold of which to mark possible drifts. Defaulted to 0.5. + :param inf_capping: The value to set for when it reached infinity. Defaulted to 10.0. + :param artifacts_tag: Tag to use for all the artifacts resulted from the function. + """ + # Loading the model: + context.logger.info(f"Loading model...") + model_handler = AutoMLRun.load_model(model_path=model, context=context) + if label_columns is None: + label_columns = [ + output.name for output in model_handler._model_artifact.spec.outputs + ] + + if feature_columns is None: + feature_columns = [ + input.name for input in model_handler._model_artifact.spec.inputs + ] + + # Get dataset by object, URL or by FeatureVector: + context.logger.info(f"Loading data...") + x, label_columns = _read_dataset_as_dataframe( + dataset=dataset, + feature_columns=feature_columns, + label_columns=label_columns, + drop_columns=drop_columns, + ) + + # Predict: + context.logger.info(f"Calculating prediction...") + y_pred = model_handler.model.predict(x, **predict_kwargs) + + # Prepare the result set: + result_set = _prepare_result_set(x=x, label_columns=label_columns, y_pred=y_pred) + + # Check for logging the result set: + if log_result_set: + # Log the result set: + context.logger.info(f"Logging result set (x | prediction)...") + context.log_dataset( + key=result_set_name, + df=result_set, + db_key=result_set_name, + tag=artifacts_tag, + ) + # Log the batch ID: + if batch_id is None: + batch_id = hashlib.sha224(str(datetime.now()).encode()).hexdigest() + context.log_result( + key="batch_id", + value=batch_id, + ) + + # Check for performing drift analysis: + if ( + perform_drift_analysis is None + and model_handler._model_artifact.spec.feature_stats is not None + ): + perform_drift_analysis = True + if perform_drift_analysis: + context.logger.info("Performing drift analysis...") + # Get the sample set statistics (either from the sample set or from the statistics logged with the model): + sample_set_statistics = _get_sample_set_statistics( + sample_set=sample_set, + model_artifact_feature_stats=model_handler._model_artifact.spec.feature_stats, + ) + # Produce the artifact: + ( + drift_table_plot, + metric_per_feature_dict, + analysis_results, + ) = _perform_drift_analysis( + sample_set_statistics=sample_set_statistics, + inputs=result_set, + drift_threshold=drift_threshold, + possible_drift_threshold=possible_drift_threshold, + inf_capping=inf_capping, + ) + # Log the artifact and results: + context.log_artifact(drift_table_plot, tag=artifacts_tag) + context.log_artifact(metric_per_feature_dict, tag=artifacts_tag) + context.log_results(results=analysis_results) diff --git a/functions/master/batch_inference/1.8.0/src/function.yaml b/functions/master/batch_inference/1.8.0/src/function.yaml new file mode 100644 index 00000000..74b672d4 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/function.yaml @@ -0,0 +1,107 @@ +kind: job +verbose: false +metadata: + name: batch-inference + tag: '' + categories: + - model-serving +spec: + image: mlrun/ml-models + entry_points: + infer: + name: infer + doc: 'Perform a prediction on a given dataset with the given model. Can perform + drift analysis between the sample set + + statistics stored in the model to the current input data. The drift rule is + the value per-feature mean of the TVD + + and Hellinger scores according to the thresholds configures here.' + parameters: + - name: context + type: MLClientCtx + doc: MLRun context. + - name: model + type: str + doc: The model Store path. + - name: dataset + type: DatasetType + doc: The dataset to infer through the model. Can be passed in `inputs` as + either a Dataset artifact / Feature vector URI. Or, in `parameters` as a + list, dictionary or numpy array. + - name: drop_columns + type: Union[str, List[str], int, List[int]] + doc: A string / integer or a list of strings / integers that represent the + column names / indices to drop. When the dataset is a list or a numpy array + this parameter must be represented by integers. + default: null + - name: label_columns + type: Union[str, List[str]] + doc: The target label(s) of the column(s) in the dataset for Regression or + Classification tasks. The label column can be accessed from the model object, + or the feature vector provided if available. + default: null + - name: feature_columns + type: Union[str, List[str]] + doc: List of feature columns that will be used to build the dataframe when + dataset is from type list or numpy array. + default: null + - name: log_result_set + type: bool + doc: Whether to log the result set - a DataFrame of the given inputs concatenated + with the predictions. Defaulted to True. + default: true + - name: result_set_name + type: str + doc: The db key to set name of the prediction result and the filename. Defaulted + to 'prediction'. + default: prediction + - name: batch_id + type: str + doc: The ID of the given batch (inference dataset). If `None`, it will be + generated. Will be logged as a result of the run. + default: null + - name: perform_drift_analysis + type: bool + doc: Whether to perform drift analysis between the sample set of the model + object to the dataset given. By default, None, which means it will perform + drift analysis if the model has a sample set statistics. Perform drift analysis + will produce a data drift table artifact. + default: null + - name: sample_set + type: DatasetType + doc: A sample dataset to give to compare the inputs in the drift analysis. + The default chosen sample set will always be the one who is set in the model + artifact itself. + default: null + - name: drift_threshold + type: float + doc: The threshold of which to mark drifts. Defaulted to 0.7. + default: 0.7 + - name: possible_drift_threshold + type: float + doc: The threshold of which to mark possible drifts. Defaulted to 0.5. + default: 0.5 + - name: inf_capping + type: float + doc: The value to set for when it reached infinity. Defaulted to 10.0. + default: 10.0 + - name: artifacts_tag + type: str + doc: Tag to use for all the artifacts resulted from the function. + default: '' + lineno: 317 + has_kwargs: true + has_varargs: false + allow_empty_resources: true + default_handler: infer + command: '' + build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg== + origin_filename: '' + auto_build: false + code_origin: '' + with_mlrun: false + disable_auto_mount: false + description: Batch inference (also knows as prediction) for the common ML frameworks + (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. diff --git a/functions/master/batch_inference/1.8.0/src/item.yaml b/functions/master/batch_inference/1.8.0/src/item.yaml new file mode 100644 index 00000000..16a56cfe --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/item.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +categories: +- model-serving +description: Batch inference (also knows as prediction) for the common ML frameworks + (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. +doc: '' +example: batch_inference.ipynb +generationDate: 2022-08-28:17-25 +hidden: false +icon: '' +labels: + author: guyl +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: batch_inference +platformVersion: 3.5.0 +spec: + extra_spec: + allow_empty_resources: true + build: + auto_build: false + with_mlrun: false + filename: batch_inference.py + handler: infer + image: mlrun/ml-models + kind: job + requirements: +url: '' +version: 1.8.0 + diff --git a/functions/master/batch_inference/1.8.0/src/requirements.txt b/functions/master/batch_inference/1.8.0/src/requirements.txt new file mode 100644 index 00000000..c120cd84 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/requirements.txt @@ -0,0 +1,4 @@ +numpy +pandas +scikit-learn +plotly \ No newline at end of file diff --git a/functions/master/batch_inference/1.8.0/src/test_batch_inference.py b/functions/master/batch_inference/1.8.0/src/test_batch_inference.py new file mode 100644 index 00000000..d18d27a9 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/src/test_batch_inference.py @@ -0,0 +1,141 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import json +import os + +import mlrun +import mlrun.common.schemas +import numpy as np +import pandas as pd +import pytest +from mlrun.frameworks.sklearn import apply_mlrun +from sklearn.datasets import make_classification +from sklearn.tree import DecisionTreeClassifier + +REQUIRED_ENV_VARS = [ + "MLRUN_DBPATH", + "V3IO_USERNAME", + "V3IO_API", + "V3IO_ACCESS_KEY", +] + + +def _validate_environment_variables() -> bool: + """ + Checks that all required Environment variables are set. + """ + environment_keys = os.environ.keys() + return all(key in environment_keys for key in REQUIRED_ENV_VARS) + + +@mlrun.handler(outputs=["training_set", "prediction_set"]) +def generate_data(n_samples: int = 5000, n_features: int = 20, n_classes: int = 2): + # Generate a classification data: + x, y = make_classification(n_samples=n_samples, n_features=n_features, n_classes=2) + + # Split the data into a training set and a prediction set: + x_train, x_prediction = x[: n_samples // 2], x[n_samples // 2 :] + y_train = y[: n_samples // 2] + + # Randomly drift some features: + x_prediction += np.random.uniform( + low=2, high=4, size=x_train.shape + ) * np.random.randint(low=0, high=2, size=x_train.shape[1], dtype=int) + + # Initialize dataframes: + features = [f"feature_{i}" for i in range(n_features)] + training_set = pd.DataFrame(data=x_train, columns=features) + training_set.insert( + loc=n_features, column="target_label", value=y_train, allow_duplicates=True + ) + prediction_set = pd.DataFrame(data=x_prediction, columns=features) + + return training_set, prediction_set + + +@mlrun.handler() +def train(training_set: pd.DataFrame): + # Get the data into x, y: + labels = pd.DataFrame(training_set["target_label"]) + training_set.drop(columns=["target_label"], inplace=True) + + # Initialize a model: + model = DecisionTreeClassifier() + + # Apply MLRun: + apply_mlrun(model=model, model_name="model") + + # Train: + model.fit(training_set, labels) + + +@pytest.mark.skipif( + condition=not _validate_environment_variables(), + reason="Project's environment variables are not set", +) +def test_batch_predict(): + + project = mlrun.get_or_create_project( + "batch-infer-v9-test", context="./", user_project=True + ) + + # Configure test: + n_samples = 5000 + n_features = 20 + + # Create the function and run: + test_function = mlrun.code_to_function(filename=__file__, kind="job") + generate_data_run = test_function.run( + handler="generate_data", + params={"n_samples": n_samples, "n_features": n_features}, + local=True, + ) + train_run = test_function.run( + handler="train", + inputs={"training_set": generate_data_run.outputs["training_set"]}, + local=True, + ) + + batch_predict_function = mlrun.import_function("function.yaml") + batch_predict_run = batch_predict_function.run( + handler="infer", + inputs={"dataset": generate_data_run.outputs["prediction_set"]}, + params={ + "model": train_run.outputs["model"], + "result_set_name": "result_set", + }, + ) + + # Check the result set: + result_set = batch_predict_run.artifact("result_set").as_df() + assert result_set.shape == (n_samples // 2, n_features + 1) + assert "target_label" in result_set.columns + assert "batch_id" in batch_predict_run.status.results + + # Check drift table artifact url + assert ( + batch_predict_run.artifact("drift_table_plot").artifact_url + == batch_predict_run.outputs["drift_table_plot"] + ) + + # Check the features drift results json: + drift_results_file = batch_predict_run.artifact("features_drift_results").local() + with open(drift_results_file, "r") as json_file: + drift_results = json.load(json_file) + assert len(drift_results) == n_features + 1 + + # Check the final analysis logged results: + assert "drift_status" in batch_predict_run.status.results + assert "drift_metric" in batch_predict_run.status.results diff --git a/functions/master/batch_inference/1.8.0/static/documentation.html b/functions/master/batch_inference/1.8.0/static/documentation.html new file mode 100644 index 00000000..e6e1e6d5 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/static/documentation.html @@ -0,0 +1,230 @@ + + + + + + + +batch_inference package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

batch_inference package

+ +
+ +
+
+ +
+
+

batch_inference package#

+
+

Submodules#

+
+
+

batch_inference.batch_inference module#

+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/batch_inference/1.8.0/static/example.html b/functions/master/batch_inference/1.8.0/static/example.html new file mode 100644 index 00000000..bbd82747 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/static/example.html @@ -0,0 +1,1762 @@ + + + + + + + +Batch Inference + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

Batch Inference#

+

A function for inferring given input through a given model while producing a Result Set and performing Data Drift Analysis.

+

In this notebook we will go over the function’s docs and outputs and see an end-to-end example of running it.

+
    +
  1. Documentation

  2. +
  3. Results Prediction

  4. +
  5. Data Drift Analysis

  6. +
  7. End-to-end Demo

  8. +
+

+
+

1. Documentation#

+

Perform a prediction on a given dataset with the given model. Can perform drift analysis between the sample set statistics stored in the model to the current input data. The drift rule is the value per-feature mean of the TVD and Hellinger scores according to the thresholds configures here.

+
+

1.1. Parameters:#

+
    +
  • context: mlrun.MLClientCtx

    +

    An MLRun context.

    +
  • +
  • model: str

    +

    The model Store path, a logged model URI.

    +
  • +
  • dataset: Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]

    +

    The dataset to infer through the model.

    +
      +
    • Can be passed in inputs as either a Dataset artifact / Feature vector URI.

    • +
    • Or, in parameters as a list, dictionary or numpy array.

    • +
    +
  • +
  • drop_columns: Union[str, List[str], int, List[int]] = None

    +

    A string / integer or a list of strings / integers that represent the column names / indices to drop. When the dataset is a list or a numpy array this parameter must be represented by integers.

    +
  • +
  • label_columns: Union[str, List[str]] = None

    +

    The target label(s) of the column(s) in the dataset. These names will be used as the column names for the predictions. The label column can be accessed from the model object, or the feature vector provided if available. The default name is "predicted_label_i" for the i column.

    +
  • +
  • feature_columns: Union[str, List[str]] = None

    +

    List of feature columns that will be used to build the dataframe when dataset is +from type list or numpy array.

    +
  • +
  • log_result_set: str = True

    +

    Whether to log the result set - a DataFrame of the given inputs concatenated with the predictions. Defaulted to True.

    +
  • +
  • result_set_name: str = "prediction"

    +

    The db key to set name of the prediction result and the filename. Defaulted to "prediction".

    +
  • +
  • batch_id: str = None

    +

    The ID of the given batch (inference dataset). If None, it will be generated. Will be logged as a result of the run.

    +
  • +
  • perform_drift_analysis: bool = None

    +

    Whether to perform drift analysis between the sample set of the model object to the dataset given. By default, None, which means it will perform drift analysis if the model has a sample set statistics.

    +
  • +
  • sample_set: Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]

    +

    A sample dataset to give to compare the inputs in the drift analysis. The default chosen sample set will always be the one who is set in the model artifact itself.

    +
      +
    • Can be passed in inputs as either a Dataset artifact / Feature vector URI.

    • +
    • Or, in parameters as a list, dictionary or numpy array.

    • +
    +
  • +
  • drift_threshold: float = 0.7

    +

    The threshold of which to mark drifts. Defaulted to 0.7.

    +
  • +
  • possible_drift_threshold: float = 0.5

    +

    The threshold of which to mark possible drifts. Defaulted to 0.5.

    +
  • +
  • inf_capping: float = 10.0

    +

    The value to set for when it reached infinity. Defaulted to 10.0.

    +
  • +
  • artifacts_tag: str = ""

    +

    Tag to use for all the artifacts resulted from the function. Defaulted to no tag.

    +
  • +
+
+
+

1.2. Outputs#

+

The outputs are split to two actions the functions can perform:

+
    +
  • Results Prediction - Will log:

    +
      +
    • A dataset artifact named by the result_set_name parameter.

    • +
    • A str result named "batch_id" of the given / generated batch ID.

    • +
    +
  • +
  • Data Drift Analysis - Will log:

    +
      +
    • A plotly artifact named "data_drift_table" with a visualization of the drifts results and histograms.

    • +
    • A json artifact named "features_drift_results" with all the features metric values.

    • +
    • A bool result named "drift_status" of the overall drift status (True if there was a drift and False otherwise).

    • +
    • A float result named "drift_score" of the overall drift metric score.

    • +
    +
  • +
+

For more details, see the next chapters.

+

+
+
+
+

2. Results Prediction#

+

The result set is a concatenated dataset of the inputs ($X$) provided and the predictions ($Y$) yielded by the model, so it will be $X | Y$.

+

For example, if the dataset given as inputs was:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

x1

x2

x3

x4

x5

+
+

And the outputs yielded by the model’s prediction was:

+
+ + + + + + + + + + + + + + + + +

y1

y2

+
+

Then the result set will be:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

x1

x2

x3

x4

x5

y1

y2

+
+

In case the parameter log_result_set is True, the outputs of the results prediction will be:

+
    +
  • The result set as described above.

  • +
  • The batch ID result - batch_id: str - a hashing result that is given by the user or generated randomly in case it was not provided to represent the batch that was being inferred.

    +
    {
    +    "batch_id": "884a0cb00d8ae16d132dd8259aac29aa78f50a9245d0e4bd58cfbf77",
    +}
    +
    +
    +
  • +
+

+
+
+

3. Data Drift Analysis#

+

The data drift analysis is done per feature using two distance measure metrics for probability distributions.

+

Let us mark our sample set as $S$ and our inputs as $I$. We will look at one feature $x$ out of $n$ features. Assuming the histograms of feature $x$ is split into 20 bins: $b_1,b_2,…,b_{20}$, we will match the feature $x$ histogram of the inputs $I$ ($x_I$) into the same bins (meaning to $x_S$) and compare their distributions using:

+
    +
  • Total Variance Distance: $TVD(x_S,x_I) = \frac{1}{2}\sum_{b_1}^{b_{20}} {|x_S - x_I|}$

  • +
  • Hellinger Distance: $H(x_S,x_I) = \sqrt{1-{\sum_{b_1}^{b_{20}}\sqrt{x_S \cdot x_I}}}$

  • +
+

Our rule then is calculating for each $x\in S: \frac{H(x_S,x_I)+TVD(x_S,x_I)}{2} < $ given thresholds.

+

In case the parameter perform_drift_analysis is True, the outputs of the analysis will be:

+
    +
  • Drift table plot - The results are presented in a plotly table artifact named "drift_table_plot" that shows each feature’s statistics and its TVD, Hellinger and KLD (Kullback–Leibler divergence) results as follows:

  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Count

Mean

Std

Min

Max

Tvd

Hellinger

Kld

Histograms

Sample

Input

Sample

Input

Sample

Input

Sample

Input

Sample

Input

x1

x2

x3

+
+
    +
  • Features drift results - A rule metric per feature dictionary is saved in a json file named "features_drift_results" where each key is a feature and its value is the feature’s metric value: Dict[str, float]

    +
    {
    +    "x1": 0.12,
    +    "x2": 0.345,
    +    "x3": 0.00678,
    +    ...
    +}
    +
    +
    +
  • +
  • In addition, two results are being added to summarize the drift analysis:

    +
      +
    • drift_status: bool - A boolean value indicating whether a drift was found.

    • +
    • drift_metric: float - The mean of all the features drift metric value (the rule above): +for $n$ features and metric rule $M(x_S,x_I)=\frac{H(x_S,x_I)+TVD(x_S,x_I)}{2}$, drift_metric $=\frac{1}{n}\sum_{x\in S}M(x_S,x_I)$

    • +
    +
    {
    +    "drift_status": True,
    +    "drift_metric": 0.81234
    +}
    +
    +
    +
  • +
+

+
+
+

4. End-to-end Demo#

+

We will see an end-to-end example that follows the steps below:

+
    +
  1. Generate data.

  2. +
  3. Train a model.

  4. +
  5. Infer data through the model using batch_predict and review the outputs.

  6. +
+
+

4.1. Code review#

+

We are using a very simple example of training a decision tree on a binary classification problem. For that we wrote two functions:

+
    +
  • generate_data - Generate a binary classification data. The data will be split into a training set and data for prediction. The data for prediction will be drifted in half of its features to showcase the plot later on.

  • +
  • train - Train a decision tree classifier on a given data.

  • +
+
+
+
# mlrun: start-code
+
+
+
+
+
+
+
# upload environment variables from env file if exists
+import os,mlrun
+
+# Specify path
+path = "/tmp/examples_ci.env"
+
+if os.path.exists(path):
+    env_dict = mlrun.set_env_from_file(path, return_dict=True)
+
+
+
+
+
+
+
import numpy as np
+import pandas as pd
+
+from sklearn.datasets import make_classification
+from sklearn.tree import DecisionTreeClassifier
+
+import mlrun
+from mlrun.frameworks.sklearn import apply_mlrun
+
+
+@mlrun.handler(outputs=["training_set", "prediction_set"])
+def generate_data(n_samples: int = 5000, n_features: int = 20):
+    # Generate a classification data:
+    x, y = make_classification(
+        n_samples=n_samples, n_features=n_features, n_classes=2
+    )
+
+    # Split the data into a training set and a prediction set:
+    x_train, x_prediction = x[: n_samples // 2], x[n_samples // 2 :]
+    y_train = y[: n_samples // 2]
+    
+    # Randomly drift some features:
+    x_prediction += (
+        np.random.uniform(low=2, high=4, size=x_train.shape) * 
+        np.random.randint(low=0, high=2, size=x_train.shape[1], dtype=int)
+    )
+    
+    # Initialize dataframes:
+    features = [f"feature_{i}" for i in range(n_features)]
+    training_set = pd.DataFrame(data=x_train, columns=features)
+    training_set.insert(
+        loc=n_features, column="label", value=y_train, allow_duplicates=True
+    )
+    prediction_set = pd.DataFrame(data=x_prediction, columns=features)
+
+    return training_set, prediction_set
+
+
+@mlrun.handler()
+def train(training_set: pd.DataFrame):
+    # Get the data into x, y:
+    labels = pd.DataFrame(training_set["label"])
+    training_set.drop(columns=["label"], inplace=True)
+
+    # Initialize a model:
+    model = DecisionTreeClassifier()
+
+    # Apply MLRun:
+    apply_mlrun(model=model, model_name="model")
+
+    # Train:
+    model.fit(training_set, labels)
+
+
+
+
+
+
+
# mlrun: end-code
+
+
+
+
+
+
+

4.2. Run the Example with MLRun#

+

First, we will prepare our MLRun functions:

+
    +
  1. We will use mlrun.code_to_function to turn this demo notebook into an MLRun function we can run.

  2. +
  3. We will use mlrun.import_function to import the batch_predict function .

  4. +
+
+
+
# Create an MLRun function to run the notebook:
+demo_function = mlrun.code_to_function(name="batch_inference_demo", kind="job")
+
+# Import the `batch_predict` function from the marketplace:
+batch_inference_function = mlrun.import_function("hub://batch_inference")
+
+# Set the desired artifact path:
+artifact_path = "./"
+
+
+
+
+

Now, we will follow the demo steps as discussed above:

+
+
+
# 1. Generate data:
+generate_data_run = demo_function.run(
+    handler="generate_data",
+    artifact_path=artifact_path,
+    local=True,
+)
+
+# 2. Train a model:
+train_run = demo_function.run(
+    handler="train",
+    artifact_path=artifact_path,
+    inputs={"training_set": generate_data_run.outputs["training_set"]},
+    local=True,
+)
+
+# 3. Perform batch prediction:
+batch_inference_run = batch_inference_function.run(
+    handler="infer",
+    artifact_path=artifact_path,
+    inputs={"dataset": generate_data_run.outputs["prediction_set"]},
+    params={
+        "model": train_run.outputs["model"],
+        "label_columns": "label",
+    },
+    local=True,
+)
+
+
+
+
+
> 2022-09-13 09:54:59,693 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
+> 2022-09-13 09:54:59,694 [info] starting run batch-predict-demo-generate_data uid=a5b1ca0a37d946e892b9305b9af833c3 DB=http://mlrun-api:8080
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:54:59completedbatch-predict-demo-generate_data
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
training_set
prediction_set
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2022-09-13 09:55:06,462 [info] run executed, status=completed
+> 2022-09-13 09:55:06,464 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
+> 2022-09-13 09:55:06,464 [info] starting run batch-predict-demo-train uid=384b36e84c4e4f91900e49e1f24ff1a6 DB=http://mlrun-api:8080
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:55:06completedbatch-predict-demo-train
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
training_set
model
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2022-09-13 09:55:07,367 [info] run executed, status=completed
+> 2022-09-13 09:55:07,370 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
+> 2022-09-13 09:55:07,370 [info] starting run batch-predict-predict uid=cf88e39d59704912a5ee41ceb539cd05 DB=http://mlrun-api:8080
+> 2022-09-13 09:55:07,703 [info] Loading model...'
+> 2022-09-13 09:55:07,753 [info] Calculating prediction...
+> 2022-09-13 09:55:07,757 [info] Logging result set (x | prediction)...
+> 2022-09-13 09:55:07,952 [info] Performing drift analysis...
+
+
+
divide by zero encountered in log
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Sep 13 09:55:07completedbatch-predict-predict
v3io_user=guyl
kind=
owner=guyl
host=jupyter-guyl-66857b7999-ffvsx
dataset
model=store://artifacts/default/model:384b36e84c4e4f91900e49e1f24ff1a6
label_columns=label
drift_status=False
drift_metric=0.3880999515903545
prediction
drift_table_plot
features_drift_results
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2022-09-13 09:55:10,078 [info] run executed, status=completed
+
+
+
+
+
+
+

4.3. Review Outputs#

+

We will review the outputs as explained in the notebook above.

+
+

4.3.1. Results Prediction#

+

First we will showcase the Result Set. As we didn’t send any name, it’s default name will be "prediction":

+
+
+
batch_inference_run.artifact("prediction").as_df()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
feature_0feature_1feature_2feature_3feature_4feature_5feature_6feature_7feature_8feature_9...feature_11feature_12feature_13feature_14feature_15feature_16feature_17feature_18feature_19label
06.3191113.2082100.793499-0.6132522.6347661.2083524.7187353.557495-2.1163110.370145...1.9629010.5813214.8445321.4087370.9649872.1114563.134610-1.7279370.3357410
13.1383781.6336610.1445570.6870032.4042791.4059903.2358921.8044262.0199801.719908...-1.4868090.6482283.0397971.8067661.2016922.4759741.4485590.9592661.1586511
22.3530264.6428790.9520970.6059774.051640-0.1575841.2187432.4647381.706084-0.250366...0.5599680.9793782.4117033.7468302.2521553.4061023.263166-0.236510-0.3131611
31.6172024.5683322.9379612.5011663.9525410.6717493.7745944.042543-2.173079-0.983443...-0.8390100.9536983.0335511.0068912.3985635.0473825.2912601.3055840.8439511
43.3442914.5383571.032059-0.0479313.1184380.4038124.4726151.840558-0.7147750.287726...1.598060-0.8055084.7420324.6087921.6177174.5148953.648923-1.3440240.6105340
..................................................................
24952.3193012.9969411.3379340.8056492.3036560.2030695.5755593.4377900.7097770.392013...-0.114619-1.4697974.5381261.2824985.6861332.8269732.445658-0.1457800.3378030
24962.9206782.1449832.153517-0.5272952.6120401.1137042.4387613.2844251.0938940.921599...-1.5868520.4098384.0947632.6366543.3334143.2511061.1329761.072658-1.2401861
24974.2566982.135673-0.1144910.3299803.935633-0.7779582.5436432.195111-0.926822-0.251254...-0.9528890.6878202.2680435.0774542.2482593.4697042.2629000.687038-0.6140661
24984.7380302.390842-0.9723291.4714612.904280-2.0790882.5706042.325262-1.602976-0.806244...0.5543990.0274934.1457283.7828024.2020063.2727090.867462-1.0200292.0133010
24992.0478312.1538130.3924840.2490103.8469100.3008463.0059972.799457-0.304962-0.990622...-0.2634730.1100912.9954112.5828434.5995353.2190911.592652-0.074851-0.6177691
+

2500 rows × 21 columns

+
+
+
+
+

4.3.2. Data Drift Analysis#

+

Second we will review the data drift table plot and the drift results:

+
+
+
batch_inference_run.artifact("drift_table_plot").show()
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
batch_inference_run.status.results
+
+
+
+
+
{'drift_status': False, 'drift_metric': 0.3880999515903545}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/batch_inference/1.8.0/static/function.html b/functions/master/batch_inference/1.8.0/static/function.html new file mode 100644 index 00000000..82e1d610 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/static/function.html @@ -0,0 +1,142 @@ + + + + + + + + + + + Source + + + + +
+        
+kind: job
+verbose: false
+metadata:
+  name: batch-inference
+  tag: ''
+  categories:
+  - model-serving
+spec:
+  image: mlrun/ml-models
+  entry_points:
+    infer:
+      name: infer
+      doc: 'Perform a prediction on a given dataset with the given model. Can perform
+        drift analysis between the sample set
+
+        statistics stored in the model to the current input data. The drift rule is
+        the value per-feature mean of the TVD
+
+        and Hellinger scores according to the thresholds configures here.'
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: MLRun context.
+      - name: model
+        type: str
+        doc: The model Store path.
+      - name: dataset
+        type: DatasetType
+        doc: The dataset to infer through the model. Can be passed in `inputs` as
+          either a Dataset artifact / Feature vector URI. Or, in `parameters` as a
+          list, dictionary or numpy array.
+      - name: drop_columns
+        type: Union[str, List[str], int, List[int]]
+        doc: A string / integer or a list of strings / integers that represent the
+          column names / indices to drop. When the dataset is a list or a numpy array
+          this parameter must be represented by integers.
+        default: null
+      - name: label_columns
+        type: Union[str, List[str]]
+        doc: The target label(s) of the column(s) in the dataset for Regression or
+          Classification tasks. The label column can be accessed from the model object,
+          or the feature vector provided if available.
+        default: null
+      - name: feature_columns
+        type: Union[str, List[str]]
+        doc: List of feature columns that will be used to build the dataframe when
+          dataset is from type list or numpy array.
+        default: null
+      - name: log_result_set
+        type: bool
+        doc: Whether to log the result set - a DataFrame of the given inputs concatenated
+          with the predictions. Defaulted to True.
+        default: true
+      - name: result_set_name
+        type: str
+        doc: The db key to set name of the prediction result and the filename. Defaulted
+          to 'prediction'.
+        default: prediction
+      - name: batch_id
+        type: str
+        doc: The ID of the given batch (inference dataset). If `None`, it will be
+          generated. Will be logged as a result of the run.
+        default: null
+      - name: perform_drift_analysis
+        type: bool
+        doc: Whether to perform drift analysis between the sample set of the model
+          object to the dataset given. By default, None, which means it will perform
+          drift analysis if the model has a sample set statistics. Perform drift analysis
+          will produce a data drift table artifact.
+        default: null
+      - name: sample_set
+        type: DatasetType
+        doc: A sample dataset to give to compare the inputs in the drift analysis.
+          The default chosen sample set will always be the one who is set in the model
+          artifact itself.
+        default: null
+      - name: drift_threshold
+        type: float
+        doc: The threshold of which to mark drifts. Defaulted to 0.7.
+        default: 0.7
+      - name: possible_drift_threshold
+        type: float
+        doc: The threshold of which to mark possible drifts. Defaulted to 0.5.
+        default: 0.5
+      - name: inf_capping
+        type: float
+        doc: The value to set for when it reached infinity. Defaulted to 10.0.
+        default: 10.0
+      - name: artifacts_tag
+        type: str
+        doc: Tag to use for all the artifacts resulted from the function.
+        default: ''
+      lineno: 317
+      has_kwargs: true
+      has_varargs: false
+  allow_empty_resources: true
+  default_handler: infer
+  command: ''
+  build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg==
+    origin_filename: ''
+    auto_build: false
+    code_origin: ''
+    with_mlrun: false
+  disable_auto_mount: false
+  description: Batch inference (also knows as prediction) for the common ML frameworks
+    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/batch_inference/1.8.0/static/item.html b/functions/master/batch_inference/1.8.0/static/item.html new file mode 100644 index 00000000..812ce885 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/static/item.html @@ -0,0 +1,66 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+- model-serving
+description: Batch inference (also knows as prediction) for the common ML frameworks
+  (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
+doc: ''
+example: batch_inference.ipynb
+generationDate: 2022-08-28:17-25
+hidden: false
+icon: ''
+labels:
+  author: guyl
+maintainers: []
+marketplaceType: ''
+mlrunVersion: 1.7.0
+name: batch_inference
+platformVersion: 3.5.0
+spec:
+  extra_spec:
+    allow_empty_resources: true
+    build:
+      auto_build: false
+      with_mlrun: false
+  filename: batch_inference.py
+  handler: infer
+  image: mlrun/ml-models
+  kind: job
+  requirements:
+url: ''
+version: 1.8.0
+
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/batch_inference/1.8.0/static/source.html b/functions/master/batch_inference/1.8.0/static/source.html new file mode 100644 index 00000000..f0aad0a5 --- /dev/null +++ b/functions/master/batch_inference/1.8.0/static/source.html @@ -0,0 +1,480 @@ + + + + + + + + + + + Source + + + + +
+        
+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import hashlib
+import json
+from datetime import datetime
+from typing import Any, Dict, List, Tuple, Union
+import semver
+
+import mlrun
+if semver.compare(mlrun.__version__, "1.5.0") >= 0:
+    raise mlrun.errors.MLRunNotFoundError(
+        f"When using `mlrun` version >=1.5.0, please use "
+        f"batch inference `v2` function ('hub://batch_inference_v2')."
+    )
+
+import mlrun.datastore
+import mlrun.utils
+import numpy as np
+import pandas as pd
+from mlrun import feature_store as fs
+from mlrun.artifacts import Artifact
+from mlrun.data_types.infer import InferOptions, get_df_stats
+from mlrun.frameworks.auto_mlrun import AutoMLRun
+from mlrun.model_monitoring.features_drift_table import FeaturesDriftTablePlot
+from mlrun.model_monitoring.model_monitoring_batch import (
+    VirtualDrift,
+    calculate_inputs_statistics,
+)
+
+# A union of all supported dataset types:
+DatasetType = Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]
+
+
+def _read_dataset_as_dataframe(
+    dataset: DatasetType,
+    feature_columns: Union[str, List[str]] = None,
+    label_columns: Union[str, List[str]] = None,
+    drop_columns: Union[str, List[str], int, List[int]] = None,
+) -> Tuple[pd.DataFrame, List[str]]:
+    """
+    Parse the given dataset into a DataFrame and drop the columns accordingly. In addition, the label columns will be
+    parsed and validated as well.
+
+    :param dataset:         A dataset that will be converted into a DataFrame.
+                            Can be either a list of lists, dict, URI or a FeatureVector.
+    :param feature_columns: List of feature columns that will be used to build the dataframe when dataset is from
+                            type list or numpy array.
+    :param label_columns:   The target label(s) of the column(s) in the dataset. for Regression or
+                            Classification tasks.
+    :param drop_columns:    ``str`` / ``int`` or a list of ``str`` / ``int`` that represent the column names / indices
+                            to drop.
+
+    :returns: A tuple of:
+              [0] = The parsed dataset as a DataFrame
+              [1] = Label columns.
+
+    raises MLRunInvalidArgumentError: If the `drop_columns` are not matching the dataset or unsupported dataset type.
+    """
+    # Turn the `drop labels` into a list if given:
+    if drop_columns is not None:
+        if not isinstance(drop_columns, list):
+            drop_columns = [drop_columns]
+
+    # Check if the dataset is in fact a Feature Vector:
+    if isinstance(dataset, fs.FeatureVector):
+        # Try to get the label columns if not provided:
+        if label_columns is None:
+            label_columns = dataset.status.label_column
+        # Get the features and parse to DataFrame:
+        dataset = fs.get_offline_features(
+            dataset.uri, drop_columns=drop_columns
+        ).to_dataframe()
+
+    elif isinstance(dataset, (list, np.ndarray)):
+        if not feature_columns:
+            raise mlrun.errors.MLRunInvalidArgumentError(
+                "Feature columns list must be provided when dataset input as from type list or numpy array"
+            )
+        # Parse the list / numpy array into a DataFrame:
+        dataset = pd.DataFrame(dataset, columns=feature_columns)
+        # Validate the `drop_columns` is given as integers:
+        if drop_columns and not all(isinstance(col, int) for col in drop_columns):
+            raise mlrun.errors.MLRunInvalidArgumentError(
+                "`drop_columns` must be an integer / list of integers if provided as a list."
+            )
+    elif isinstance(dataset, mlrun.DataItem):
+        # Turn the DataITem to DataFrame:
+        dataset = dataset.as_df()
+    else:
+        # Parse the object (should be a pd.DataFrame / pd.Series, dictionary) into a DataFrame:
+        try:
+            dataset = pd.DataFrame(dataset)
+        except ValueError as e:
+            raise mlrun.errors.MLRunInvalidArgumentError(
+                f"Could not parse the given dataset of type {type(dataset)} into a pandas DataFrame. "
+                f"Received the following error: {e}"
+            )
+    # Drop columns if needed:
+    if drop_columns:
+        dataset.drop(drop_columns, axis=1, inplace=True)
+
+    # Turn the `label_columns` into a list by default:
+    if label_columns is None:
+        label_columns = []
+    elif isinstance(label_columns, (str, int)):
+        label_columns = [label_columns]
+    return dataset, label_columns
+
+
+def _prepare_result_set(
+    x: pd.DataFrame, label_columns: List[str], y_pred: np.ndarray
+) -> pd.DataFrame:
+    """
+    Set default label column names and validate given names to prepare the result set - a concatenation of the inputs
+    (x) and the model predictions (y_pred).
+
+    :param x:             The inputs.
+    :param label_columns: A list of strings representing the target column names to add to the predictions. Default name
+                          will be used in case the list is empty (predicted_label_{i}).
+    :param y_pred:        The model predictions on the inputs.
+
+    :returns: The result set.
+
+    raises MLRunInvalidArgumentError: If the labels columns amount do not match the outputs or if one of the label
+                                       column already exists in the dataset.
+    """
+    # Prepare default target columns names if not provided:
+    prediction_columns_amount = 1 if len(y_pred.shape) == 1 else y_pred.shape[1]
+    if len(label_columns) == 0:
+        # Add default label column names:
+        if prediction_columns_amount == 1:
+            label_columns = ["predicted_label"]
+        else:
+            label_columns = [
+                f"predicted_label_{i}" for i in range(prediction_columns_amount)
+            ]
+
+    # Validate the label columns:
+    if prediction_columns_amount != len(label_columns):
+        # No equality between provided label column names and outputs amount:
+        raise mlrun.errors.MLRunInvalidArgumentError(
+            f"The number of predicted labels: {prediction_columns_amount} "
+            f"is not equal to the given label columns: {len(label_columns)}"
+        )
+    common_labels = set(label_columns) & set(x.columns.tolist())
+    if common_labels:
+        # Label column exist in the original inputs:
+        raise mlrun.errors.MLRunInvalidArgumentError(
+            f"The labels: {common_labels} are already existed in the given dataset."
+        )
+
+    return pd.concat(
+        [x, pd.DataFrame(y_pred, columns=label_columns, index=x.index)], axis=1
+    )
+
+
+def _get_sample_set_statistics(
+    sample_set: DatasetType = None, model_artifact_feature_stats: dict = None
+) -> dict:
+    """
+    Get the sample set statistics either from the given sample set or the statistics logged with the model while
+    favoring the given sample set.
+
+    :param sample_set:                   A sample dataset to give to compare the inputs in the drift analysis.
+    :param model_artifact_feature_stats: The `feature_stats` attribute in the spec of the model artifact, where the
+                                         original sample set statistics of the model was used.
+
+    :returns: The sample set statistics.
+
+    raises MLRunInvalidArgumentError: If no sample set or statistics were given.
+    """
+    # Check if a sample set was provided:
+    if sample_set is None:
+        # Check if the model was logged with a sample set:
+        if model_artifact_feature_stats is None:
+            raise mlrun.errors.MLRunInvalidArgumentError(
+                "Cannot perform drift analysis as there is no sample set to compare to. The model artifact was not "
+                "logged with a sample set and `sample_set` was not provided to the function."
+            )
+        # Return the statistics logged with the model:
+        return model_artifact_feature_stats
+
+    # Turn the DataItem to DataFrame:
+    if isinstance(sample_set, mlrun.DataItem):
+        sample_set, _ = _read_dataset_as_dataframe(dataset=sample_set)
+
+    # Return the sample set statistics:
+    return get_df_stats(df=sample_set, options=InferOptions.Histogram)
+
+
+def _get_drift_result(
+    tvd: float,
+    hellinger: float,
+    threshold: float,
+) -> Tuple[bool, float]:
+    """
+    Calculate the drift result by the following equation: (tvd + hellinger) / 2
+
+    :param tvd:       The feature's TVD value.
+    :param hellinger: The feature's Hellinger value.
+    :param threshold: The threshold from which the value is considered a drift.
+
+    :returns: A tuple of:
+              [0] = Boolean value as the drift status.
+              [1] = The result.
+    """
+    result = (tvd + hellinger) / 2
+    if result >= threshold:
+        return True, result
+    return False, result
+
+
+def _perform_drift_analysis(
+    sample_set_statistics: dict,
+    inputs: pd.DataFrame,
+    drift_threshold: float,
+    possible_drift_threshold: float,
+    inf_capping: float,
+) -> Tuple[Artifact, Artifact, dict]:
+    """
+    Perform drift analysis, producing the drift table artifact for logging post prediction.
+
+    :param sample_set_statistics:    The statistics of the sample set logged along a model.
+    :param inputs:                   Input dataset to perform the drift calculation on.
+    :param drift_threshold:          The threshold of which to mark drifts.
+    :param possible_drift_threshold: The threshold of which to mark possible drifts.
+    :param inf_capping:              The value to set for when it reached infinity.
+
+    :returns: A tuple of
+              [0] = An MLRun artifact holding the HTML code of the drift table plot.
+              [1] = An MLRun artifact holding the metric per feature dictionary.
+              [2] = Results to log the final analysis outcome.
+    """
+    # Calculate the input's statistics:
+    inputs_statistics = calculate_inputs_statistics(
+        sample_set_statistics=sample_set_statistics,
+        inputs=inputs,
+    )
+
+    # Calculate drift:
+    virtual_drift = VirtualDrift(inf_capping=inf_capping)
+    metrics = virtual_drift.compute_drift_from_histograms(
+        feature_stats=sample_set_statistics,
+        current_stats=inputs_statistics,
+    )
+    drift_results = virtual_drift.check_for_drift_per_feature(
+        metrics_results_dictionary=metrics,
+        possible_drift_threshold=possible_drift_threshold,
+        drift_detected_threshold=drift_threshold,
+    )
+
+    # Validate all feature columns named the same between the inputs and sample sets:
+    sample_features = set(
+        [
+            feature_name
+            for feature_name, feature_statistics in sample_set_statistics.items()
+            if isinstance(feature_statistics, dict)
+        ]
+    )
+    input_features = set(inputs.columns)
+    if len(sample_features & input_features) != len(input_features):
+        raise mlrun.errors.MLRunInvalidArgumentError(
+            f"Not all feature names were matching between the inputs and the sample set provided: "
+            f"{input_features - sample_features | sample_features - input_features}"
+        )
+
+    # Plot:
+    html_plot = FeaturesDriftTablePlot().produce(
+        features=list(input_features),
+        sample_set_statistics=sample_set_statistics,
+        inputs_statistics=inputs_statistics,
+        metrics=metrics,
+        drift_results=drift_results,
+    )
+
+    # Prepare metrics per feature dictionary:
+    metrics_per_feature = {
+        feature: _get_drift_result(
+            tvd=metric_dictionary["tvd"],
+            hellinger=metric_dictionary["hellinger"],
+            threshold=drift_threshold,
+        )[1]
+        for feature, metric_dictionary in metrics.items()
+        if isinstance(metric_dictionary, dict)
+    }
+
+    # Calculate the final analysis result:
+    drift_status, drift_metric = _get_drift_result(
+        tvd=metrics["tvd_mean"],
+        hellinger=metrics["hellinger_mean"],
+        threshold=drift_threshold,
+    )
+
+    return (
+        Artifact(body=html_plot, format="html", key="drift_table_plot"),
+        Artifact(
+            body=json.dumps(metrics_per_feature),
+            format="json",
+            key="features_drift_results",
+        ),
+        {"drift_status": drift_status, "drift_metric": drift_metric},
+    )
+
+
+def infer(
+    context: mlrun.MLClientCtx,
+    model: str,
+    dataset: DatasetType,
+    drop_columns: Union[str, List[str], int, List[int]] = None,
+    label_columns: Union[str, List[str]] = None,
+    feature_columns: Union[str, List[str]] = None,
+    log_result_set: bool = True,
+    result_set_name: str = "prediction",
+    batch_id: str = None,
+    perform_drift_analysis: bool = None,
+    sample_set: DatasetType = None,
+    drift_threshold: float = 0.7,
+    possible_drift_threshold: float = 0.5,
+    inf_capping: float = 10.0,
+    artifacts_tag: str = "",
+    **predict_kwargs: Dict[str, Any],
+):
+    """
+    Perform a prediction on a given dataset with the given model. Can perform drift analysis between the sample set
+    statistics stored in the model to the current input data. The drift rule is the value per-feature mean of the TVD
+    and Hellinger scores according to the thresholds configures here.
+
+    :param context:                  MLRun context.
+    :param model:                    The model Store path.
+    :param dataset:                  The dataset to infer through the model. Can be passed in `inputs` as either a
+                                     Dataset artifact / Feature vector URI. Or, in `parameters` as a list, dictionary or
+                                     numpy array.
+    :param drop_columns:             A string / integer or a list of strings / integers that represent the column names
+                                     / indices to drop. When the dataset is a list or a numpy array this parameter must
+                                     be represented by integers.
+    :param label_columns:            The target label(s) of the column(s) in the dataset for Regression or
+                                     Classification tasks. The label column can be accessed from the model object, or
+                                     the feature vector provided if available.
+    :param feature_columns:          List of feature columns that will be used to build the dataframe when dataset is
+                                     from type list or numpy array.
+    :param log_result_set:           Whether to log the result set - a DataFrame of the given inputs concatenated with
+                                     the predictions. Defaulted to True.
+    :param result_set_name:          The db key to set name of the prediction result and the filename. Defaulted to
+                                     'prediction'.
+    :param batch_id:                 The ID of the given batch (inference dataset). If `None`, it will be generated.
+                                     Will be logged as a result of the run.
+    :param perform_drift_analysis:   Whether to perform drift analysis between the sample set of the model object to the
+                                     dataset given. By default, None, which means it will perform drift analysis if the
+                                     model has a sample set statistics. Perform drift analysis will produce a data drift
+                                     table artifact.
+    :param sample_set:               A sample dataset to give to compare the inputs in the drift analysis. The default
+                                     chosen sample set will always be the one who is set in the model artifact itself.
+    :param drift_threshold:          The threshold of which to mark drifts. Defaulted to 0.7.
+    :param possible_drift_threshold: The threshold of which to mark possible drifts. Defaulted to 0.5.
+    :param inf_capping:              The value to set for when it reached infinity. Defaulted to 10.0.
+    :param artifacts_tag:            Tag to use for all the artifacts resulted from the function.
+    """
+    # Loading the model:
+    context.logger.info(f"Loading model...")
+    model_handler = AutoMLRun.load_model(model_path=model, context=context)
+    if label_columns is None:
+        label_columns = [
+            output.name for output in model_handler._model_artifact.spec.outputs
+        ]
+
+    if feature_columns is None:
+        feature_columns = [
+            input.name for input in model_handler._model_artifact.spec.inputs
+        ]
+
+    # Get dataset by object, URL or by FeatureVector:
+    context.logger.info(f"Loading data...")
+    x, label_columns = _read_dataset_as_dataframe(
+        dataset=dataset,
+        feature_columns=feature_columns,
+        label_columns=label_columns,
+        drop_columns=drop_columns,
+    )
+
+    # Predict:
+    context.logger.info(f"Calculating prediction...")
+    y_pred = model_handler.model.predict(x, **predict_kwargs)
+
+    # Prepare the result set:
+    result_set = _prepare_result_set(x=x, label_columns=label_columns, y_pred=y_pred)
+
+    # Check for logging the result set:
+    if log_result_set:
+        # Log the result set:
+        context.logger.info(f"Logging result set (x | prediction)...")
+        context.log_dataset(
+            key=result_set_name,
+            df=result_set,
+            db_key=result_set_name,
+            tag=artifacts_tag,
+        )
+        # Log the batch ID:
+        if batch_id is None:
+            batch_id = hashlib.sha224(str(datetime.now()).encode()).hexdigest()
+        context.log_result(
+            key="batch_id",
+            value=batch_id,
+        )
+
+    # Check for performing drift analysis:
+    if (
+        perform_drift_analysis is None
+        and model_handler._model_artifact.spec.feature_stats is not None
+    ):
+        perform_drift_analysis = True
+    if perform_drift_analysis:
+        context.logger.info("Performing drift analysis...")
+        # Get the sample set statistics (either from the sample set or from the statistics logged with the model):
+        sample_set_statistics = _get_sample_set_statistics(
+            sample_set=sample_set,
+            model_artifact_feature_stats=model_handler._model_artifact.spec.feature_stats,
+        )
+        # Produce the artifact:
+        (
+            drift_table_plot,
+            metric_per_feature_dict,
+            analysis_results,
+        ) = _perform_drift_analysis(
+            sample_set_statistics=sample_set_statistics,
+            inputs=result_set,
+            drift_threshold=drift_threshold,
+            possible_drift_threshold=possible_drift_threshold,
+            inf_capping=inf_capping,
+        )
+        # Log the artifact and results:
+        context.log_artifact(drift_table_plot, tag=artifacts_tag)
+        context.log_artifact(metric_per_feature_dict, tag=artifacts_tag)
+        context.log_results(results=analysis_results)
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/batch_inference/latest/src/function.yaml b/functions/master/batch_inference/latest/src/function.yaml index cdee5641..74b672d4 100644 --- a/functions/master/batch_inference/latest/src/function.yaml +++ b/functions/master/batch_inference/latest/src/function.yaml @@ -1,25 +1,12 @@ kind: job +verbose: false metadata: name: batch-inference tag: '' - hash: c7b8439a70292e916788a04dce35e57e00b1a41c - project: '' - labels: - author: guyl categories: - - utils + - model-serving spec: - command: '' - args: [] image: mlrun/ml-models - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg== - commands: [] - code_origin: '' - origin_filename: '' - with_mlrun: false - auto_build: false - requirements: [] entry_points: infer: name: infer @@ -103,19 +90,18 @@ spec: type: str doc: Tag to use for all the artifacts resulted from the function. default: '' - outputs: - - default: '' lineno: 317 - description: Batch inference (also knows as prediction) for the common ML frameworks - (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. + has_kwargs: true + has_varargs: false + allow_empty_resources: true default_handler: infer + command: '' + build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg== + origin_filename: '' + auto_build: false + code_origin: '' + with_mlrun: false disable_auto_mount: false - allow_empty_resources: true - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false + description: Batch inference (also knows as prediction) for the common ML frameworks + (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. diff --git a/functions/master/batch_inference/latest/src/item.yaml b/functions/master/batch_inference/latest/src/item.yaml index 125fb525..16a56cfe 100644 --- a/functions/master/batch_inference/latest/src/item.yaml +++ b/functions/master/batch_inference/latest/src/item.yaml @@ -1,6 +1,6 @@ apiVersion: v1 categories: -- utils +- model-serving description: Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. doc: '' @@ -12,7 +12,7 @@ labels: author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.1 +mlrunVersion: 1.7.0 name: batch_inference platformVersion: 3.5.0 spec: @@ -27,5 +27,5 @@ spec: kind: job requirements: url: '' -version: 1.7.0 +version: 1.8.0 diff --git a/functions/master/batch_inference/latest/static/documentation.html b/functions/master/batch_inference/latest/static/documentation.html index d37b20b7..e6e1e6d5 100644 --- a/functions/master/batch_inference/latest/static/documentation.html +++ b/functions/master/batch_inference/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/batch_inference/latest/static/example.html b/functions/master/batch_inference/latest/static/example.html index 63c52c56..bbd82747 100644 --- a/functions/master/batch_inference/latest/static/example.html +++ b/functions/master/batch_inference/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/batch_inference/latest/static/function.html b/functions/master/batch_inference/latest/static/function.html index afa44256..82e1d610 100644 --- a/functions/master/batch_inference/latest/static/function.html +++ b/functions/master/batch_inference/latest/static/function.html @@ -29,27 +29,14 @@
         
 kind: job
+verbose: false
 metadata:
   name: batch-inference
   tag: ''
-  hash: c7b8439a70292e916788a04dce35e57e00b1a41c
-  project: ''
-  labels:
-    author: guyl
   categories:
-  - utils
+  - model-serving
 spec:
-  command: ''
-  args: []
   image: mlrun/ml-models
-  build:
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg==
-    commands: []
-    code_origin: ''
-    origin_filename: ''
-    with_mlrun: false
-    auto_build: false
-    requirements: []
   entry_points:
     infer:
       name: infer
@@ -133,22 +120,21 @@
         type: str
         doc: Tag to use for all the artifacts resulted from the function.
         default: ''
-      outputs:
-      - default: ''
       lineno: 317
-  description: Batch inference (also knows as prediction) for the common ML frameworks
-    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
+      has_kwargs: true
+      has_varargs: false
+  allow_empty_resources: true
   default_handler: infer
+  command: ''
+  build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IGhhc2hsaWIKaW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCmltcG9ydCBzZW12ZXIKCmltcG9ydCBtbHJ1bgppZiBzZW12ZXIuY29tcGFyZShtbHJ1bi5fX3ZlcnNpb25fXywgIjEuNS4wIikgPj0gMDoKICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bk5vdEZvdW5kRXJyb3IoCiAgICAgICAgZiJXaGVuIHVzaW5nIGBtbHJ1bmAgdmVyc2lvbiA+PTEuNS4wLCBwbGVhc2UgdXNlICIKICAgICAgICBmImJhdGNoIGluZmVyZW5jZSBgdjJgIGZ1bmN0aW9uICgnaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyJykuIgogICAgKQoKaW1wb3J0IG1scnVuLmRhdGFzdG9yZQppbXBvcnQgbWxydW4udXRpbHMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1biBpbXBvcnQgZmVhdHVyZV9zdG9yZSBhcyBmcwpmcm9tIG1scnVuLmFydGlmYWN0cyBpbXBvcnQgQXJ0aWZhY3QKZnJvbSBtbHJ1bi5kYXRhX3R5cGVzLmluZmVyIGltcG9ydCBJbmZlck9wdGlvbnMsIGdldF9kZl9zdGF0cwpmcm9tIG1scnVuLmZyYW1ld29ya3MuYXV0b19tbHJ1biBpbXBvcnQgQXV0b01MUnVuCmZyb20gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5mZWF0dXJlc19kcmlmdF90YWJsZSBpbXBvcnQgRmVhdHVyZXNEcmlmdFRhYmxlUGxvdApmcm9tIG1scnVuLm1vZGVsX21vbml0b3JpbmcubW9kZWxfbW9uaXRvcmluZ19iYXRjaCBpbXBvcnQgKAogICAgVmlydHVhbERyaWZ0LAogICAgY2FsY3VsYXRlX2lucHV0c19zdGF0aXN0aWNzLAopCgojIEEgdW5pb24gb2YgYWxsIHN1cHBvcnRlZCBkYXRhc2V0IHR5cGVzOgpEYXRhc2V0VHlwZSA9IFVuaW9uW21scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheV0KCgpkZWYgX3JlYWRfZGF0YXNldF9hc19kYXRhZnJhbWUoCiAgICBkYXRhc2V0OiBEYXRhc2V0VHlwZSwKICAgIGZlYXR1cmVfY29sdW1uczogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgTGlzdFtzdHJdXToKICAgICIiIgogICAgUGFyc2UgdGhlIGdpdmVuIGRhdGFzZXQgaW50byBhIERhdGFGcmFtZSBhbmQgZHJvcCB0aGUgY29sdW1ucyBhY2NvcmRpbmdseS4gSW4gYWRkaXRpb24sIHRoZSBsYWJlbCBjb2x1bW5zIHdpbGwgYmUKICAgIHBhcnNlZCBhbmQgdmFsaWRhdGVkIGFzIHdlbGwuCgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgQSBkYXRhc2V0IHRoYXQgd2lsbCBiZSBjb252ZXJ0ZWQgaW50byBhIERhdGFGcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbiBiZSBlaXRoZXIgYSBsaXN0IG9mIGxpc3RzLCBkaWN0LCBVUkkgb3IgYSBGZWF0dXJlVmVjdG9yLgogICAgOnBhcmFtIGZlYXR1cmVfY29sdW1uczogTGlzdCBvZiBmZWF0dXJlIGNvbHVtbnMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgdGhlIGRhdGFmcmFtZSB3aGVuIGRhdGFzZXQgaXMgZnJvbQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5LgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0LiBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICBgYHN0cmBgIC8gYGBpbnRgYCBvciBhIGxpc3Qgb2YgYGBzdHJgYCAvIGBgaW50YGAgdGhhdCByZXByZXNlbnQgdGhlIGNvbHVtbiBuYW1lcyAvIGluZGljZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvIGRyb3AuCgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgWzBdID0gVGhlIHBhcnNlZCBkYXRhc2V0IGFzIGEgRGF0YUZyYW1lCiAgICAgICAgICAgICAgWzFdID0gTGFiZWwgY29sdW1ucy4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGBkcm9wX2NvbHVtbnNgIGFyZSBub3QgbWF0Y2hpbmcgdGhlIGRhdGFzZXQgb3IgdW5zdXBwb3J0ZWQgZGF0YXNldCB0eXBlLgogICAgIiIiCiAgICAjIFR1cm4gdGhlIGBkcm9wIGxhYmVsc2AgaW50byBhIGxpc3QgaWYgZ2l2ZW46CiAgICBpZiBkcm9wX2NvbHVtbnMgaXMgbm90IE5vbmU6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2UoZHJvcF9jb2x1bW5zLCBsaXN0KToKICAgICAgICAgICAgZHJvcF9jb2x1bW5zID0gW2Ryb3BfY29sdW1uc10KCiAgICAjIENoZWNrIGlmIHRoZSBkYXRhc2V0IGlzIGluIGZhY3QgYSBGZWF0dXJlIFZlY3RvcjoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YXNldCwgZnMuRmVhdHVyZVZlY3Rvcik6CiAgICAgICAgIyBUcnkgdG8gZ2V0IHRoZSBsYWJlbCBjb2x1bW5zIGlmIG5vdCBwcm92aWRlZDoKICAgICAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBkYXRhc2V0LnN0YXR1cy5sYWJlbF9jb2x1bW4KICAgICAgICAjIEdldCB0aGUgZmVhdHVyZXMgYW5kIHBhcnNlIHRvIERhdGFGcmFtZToKICAgICAgICBkYXRhc2V0ID0gZnMuZ2V0X29mZmxpbmVfZmVhdHVyZXMoCiAgICAgICAgICAgIGRhdGFzZXQudXJpLCBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zCiAgICAgICAgKS50b19kYXRhZnJhbWUoKQoKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCAobGlzdCwgbnAubmRhcnJheSkpOgogICAgICAgIGlmIG5vdCBmZWF0dXJlX2NvbHVtbnM6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkZlYXR1cmUgY29sdW1ucyBsaXN0IG11c3QgYmUgcHJvdmlkZWQgd2hlbiBkYXRhc2V0IGlucHV0IGFzIGZyb20gdHlwZSBsaXN0IG9yIG51bXB5IGFycmF5IgogICAgICAgICAgICApCiAgICAgICAgIyBQYXJzZSB0aGUgbGlzdCAvIG51bXB5IGFycmF5IGludG8gYSBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IHBkLkRhdGFGcmFtZShkYXRhc2V0LCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykKICAgICAgICAjIFZhbGlkYXRlIHRoZSBgZHJvcF9jb2x1bW5zYCBpcyBnaXZlbiBhcyBpbnRlZ2VyczoKICAgICAgICBpZiBkcm9wX2NvbHVtbnMgYW5kIG5vdCBhbGwoaXNpbnN0YW5jZShjb2wsIGludCkgZm9yIGNvbCBpbiBkcm9wX2NvbHVtbnMpOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgICJgZHJvcF9jb2x1bW5zYCBtdXN0IGJlIGFuIGludGVnZXIgLyBsaXN0IG9mIGludGVnZXJzIGlmIHByb3ZpZGVkIGFzIGEgbGlzdC4iCiAgICAgICAgICAgICkKICAgIGVsaWYgaXNpbnN0YW5jZShkYXRhc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgIyBUdXJuIHRoZSBEYXRhSVRlbSB0byBEYXRhRnJhbWU6CiAgICAgICAgZGF0YXNldCA9IGRhdGFzZXQuYXNfZGYoKQogICAgZWxzZToKICAgICAgICAjIFBhcnNlIHRoZSBvYmplY3QgKHNob3VsZCBiZSBhIHBkLkRhdGFGcmFtZSAvIHBkLlNlcmllcywgZGljdGlvbmFyeSkgaW50byBhIERhdGFGcmFtZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGRhdGFzZXQgPSBwZC5EYXRhRnJhbWUoZGF0YXNldCkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgICAgIGYiQ291bGQgbm90IHBhcnNlIHRoZSBnaXZlbiBkYXRhc2V0IG9mIHR5cGUge3R5cGUoZGF0YXNldCl9IGludG8gYSBwYW5kYXMgRGF0YUZyYW1lLiAiCiAgICAgICAgICAgICAgICBmIlJlY2VpdmVkIHRoZSBmb2xsb3dpbmcgZXJyb3I6IHtlfSIKICAgICAgICAgICAgKQogICAgIyBEcm9wIGNvbHVtbnMgaWYgbmVlZGVkOgogICAgaWYgZHJvcF9jb2x1bW5zOgogICAgICAgIGRhdGFzZXQuZHJvcChkcm9wX2NvbHVtbnMsIGF4aXM9MSwgaW5wbGFjZT1UcnVlKQoKICAgICMgVHVybiB0aGUgYGxhYmVsX2NvbHVtbnNgIGludG8gYSBsaXN0IGJ5IGRlZmF1bHQ6CiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtdCiAgICBlbGlmIGlzaW5zdGFuY2UobGFiZWxfY29sdW1ucywgKHN0ciwgaW50KSk6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFtsYWJlbF9jb2x1bW5zXQogICAgcmV0dXJuIGRhdGFzZXQsIGxhYmVsX2NvbHVtbnMKCgpkZWYgX3ByZXBhcmVfcmVzdWx0X3NldCgKICAgIHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkKKSAtPiBwZC5EYXRhRnJhbWU6CiAgICAiIiIKICAgIFNldCBkZWZhdWx0IGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgdmFsaWRhdGUgZ2l2ZW4gbmFtZXMgdG8gcHJlcGFyZSB0aGUgcmVzdWx0IHNldCAtIGEgY29uY2F0ZW5hdGlvbiBvZiB0aGUgaW5wdXRzCiAgICAoeCkgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoeV9wcmVkKS4KCiAgICA6cGFyYW0geDogICAgICAgICAgICAgVGhlIGlucHV0cy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiBBIGxpc3Qgb2Ygc3RyaW5ncyByZXByZXNlbnRpbmcgdGhlIHRhcmdldCBjb2x1bW4gbmFtZXMgdG8gYWRkIHRvIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGluIGNhc2UgdGhlIGxpc3QgaXMgZW1wdHkgKHByZWRpY3RlZF9sYWJlbF97aX0pLgogICAgOnBhcmFtIHlfcHJlZDogICAgICAgIFRoZSBtb2RlbCBwcmVkaWN0aW9ucyBvbiB0aGUgaW5wdXRzLgoKICAgIDpyZXR1cm5zOiBUaGUgcmVzdWx0IHNldC4KCiAgICByYWlzZXMgTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcjogSWYgdGhlIGxhYmVscyBjb2x1bW5zIGFtb3VudCBkbyBub3QgbWF0Y2ggdGhlIG91dHB1dHMgb3IgaWYgb25lIG9mIHRoZSBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gYWxyZWFkeSBleGlzdHMgaW4gdGhlIGRhdGFzZXQuCiAgICAiIiIKICAgICMgUHJlcGFyZSBkZWZhdWx0IHRhcmdldCBjb2x1bW5zIG5hbWVzIGlmIG5vdCBwcm92aWRlZDoKICAgIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPSAxIGlmIGxlbih5X3ByZWQuc2hhcGUpID09IDEgZWxzZSB5X3ByZWQuc2hhcGVbMV0KICAgIGlmIGxlbihsYWJlbF9jb2x1bW5zKSA9PSAwOgogICAgICAgICMgQWRkIGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzOgogICAgICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgPT0gMToKICAgICAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsicHJlZGljdGVkX2xhYmVsIl0KICAgICAgICBlbHNlOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICAgICAgZiJwcmVkaWN0ZWRfbGFiZWxfe2l9IiBmb3IgaSBpbiByYW5nZShwcmVkaWN0aW9uX2NvbHVtbnNfYW1vdW50KQogICAgICAgICAgICBdCgogICAgIyBWYWxpZGF0ZSB0aGUgbGFiZWwgY29sdW1uczoKICAgIGlmIHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQgIT0gbGVuKGxhYmVsX2NvbHVtbnMpOgogICAgICAgICMgTm8gZXF1YWxpdHkgYmV0d2VlbiBwcm92aWRlZCBsYWJlbCBjb2x1bW4gbmFtZXMgYW5kIG91dHB1dHMgYW1vdW50OgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBudW1iZXIgb2YgcHJlZGljdGVkIGxhYmVsczoge3ByZWRpY3Rpb25fY29sdW1uc19hbW91bnR9ICIKICAgICAgICAgICAgZiJpcyBub3QgZXF1YWwgdG8gdGhlIGdpdmVuIGxhYmVsIGNvbHVtbnM6IHtsZW4obGFiZWxfY29sdW1ucyl9IgogICAgICAgICkKICAgIGNvbW1vbl9sYWJlbHMgPSBzZXQobGFiZWxfY29sdW1ucykgJiBzZXQoeC5jb2x1bW5zLnRvbGlzdCgpKQogICAgaWYgY29tbW9uX2xhYmVsczoKICAgICAgICAjIExhYmVsIGNvbHVtbiBleGlzdCBpbiB0aGUgb3JpZ2luYWwgaW5wdXRzOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIlRoZSBsYWJlbHM6IHtjb21tb25fbGFiZWxzfSBhcmUgYWxyZWFkeSBleGlzdGVkIGluIHRoZSBnaXZlbiBkYXRhc2V0LiIKICAgICAgICApCgogICAgcmV0dXJuIHBkLmNvbmNhdCgKICAgICAgICBbeCwgcGQuRGF0YUZyYW1lKHlfcHJlZCwgY29sdW1ucz1sYWJlbF9jb2x1bW5zLCBpbmRleD14LmluZGV4KV0sIGF4aXM9MQogICAgKQoKCmRlZiBfZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygKICAgIHNhbXBsZV9zZXQ6IERhdGFzZXRUeXBlID0gTm9uZSwgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCA9IE5vbmUKKSAtPiBkaWN0OgogICAgIiIiCiAgICBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBlaXRoZXIgZnJvbSB0aGUgZ2l2ZW4gc2FtcGxlIHNldCBvciB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwgd2hpbGUKICAgIGZhdm9yaW5nIHRoZSBnaXZlbiBzYW1wbGUgc2V0LgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0OiAgICAgICAgICAgICAgICAgICBBIHNhbXBsZSBkYXRhc2V0IHRvIGdpdmUgdG8gY29tcGFyZSB0aGUgaW5wdXRzIGluIHRoZSBkcmlmdCBhbmFseXNpcy4KICAgIDpwYXJhbSBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzOiBUaGUgYGZlYXR1cmVfc3RhdHNgIGF0dHJpYnV0ZSBpbiB0aGUgc3BlYyBvZiB0aGUgbW9kZWwgYXJ0aWZhY3QsIHdoZXJlIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsIHNhbXBsZSBzZXQgc3RhdGlzdGljcyBvZiB0aGUgbW9kZWwgd2FzIHVzZWQuCgogICAgOnJldHVybnM6IFRoZSBzYW1wbGUgc2V0IHN0YXRpc3RpY3MuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IElmIG5vIHNhbXBsZSBzZXQgb3Igc3RhdGlzdGljcyB3ZXJlIGdpdmVuLgogICAgIiIiCiAgICAjIENoZWNrIGlmIGEgc2FtcGxlIHNldCB3YXMgcHJvdmlkZWQ6CiAgICBpZiBzYW1wbGVfc2V0IGlzIE5vbmU6CiAgICAgICAgIyBDaGVjayBpZiB0aGUgbW9kZWwgd2FzIGxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldDoKICAgICAgICBpZiBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICAgICAgIkNhbm5vdCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGFzIHRoZXJlIGlzIG5vIHNhbXBsZSBzZXQgdG8gY29tcGFyZSB0by4gVGhlIG1vZGVsIGFydGlmYWN0IHdhcyBub3QgIgogICAgICAgICAgICAgICAgImxvZ2dlZCB3aXRoIGEgc2FtcGxlIHNldCBhbmQgYHNhbXBsZV9zZXRgIHdhcyBub3QgcHJvdmlkZWQgdG8gdGhlIGZ1bmN0aW9uLiIKICAgICAgICAgICAgKQogICAgICAgICMgUmV0dXJuIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbDoKICAgICAgICByZXR1cm4gbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0cwoKICAgICMgVHVybiB0aGUgRGF0YUl0ZW0gdG8gRGF0YUZyYW1lOgogICAgaWYgaXNpbnN0YW5jZShzYW1wbGVfc2V0LCBtbHJ1bi5EYXRhSXRlbSk6CiAgICAgICAgc2FtcGxlX3NldCwgXyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKGRhdGFzZXQ9c2FtcGxlX3NldCkKCiAgICAjIFJldHVybiB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzOgogICAgcmV0dXJuIGdldF9kZl9zdGF0cyhkZj1zYW1wbGVfc2V0LCBvcHRpb25zPUluZmVyT3B0aW9ucy5IaXN0b2dyYW0pCgoKZGVmIF9nZXRfZHJpZnRfcmVzdWx0KAogICAgdHZkOiBmbG9hdCwKICAgIGhlbGxpbmdlcjogZmxvYXQsCiAgICB0aHJlc2hvbGQ6IGZsb2F0LAopIC0+IFR1cGxlW2Jvb2wsIGZsb2F0XToKICAgICIiIgogICAgQ2FsY3VsYXRlIHRoZSBkcmlmdCByZXN1bHQgYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjogKHR2ZCArIGhlbGxpbmdlcikgLyAyCgogICAgOnBhcmFtIHR2ZDogICAgICAgVGhlIGZlYXR1cmUncyBUVkQgdmFsdWUuCiAgICA6cGFyYW0gaGVsbGluZ2VyOiBUaGUgZmVhdHVyZSdzIEhlbGxpbmdlciB2YWx1ZS4KICAgIDpwYXJhbSB0aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgZnJvbSB3aGljaCB0aGUgdmFsdWUgaXMgY29uc2lkZXJlZCBhIGRyaWZ0LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgogICAgICAgICAgICAgIFswXSA9IEJvb2xlYW4gdmFsdWUgYXMgdGhlIGRyaWZ0IHN0YXR1cy4KICAgICAgICAgICAgICBbMV0gPSBUaGUgcmVzdWx0LgogICAgIiIiCiAgICByZXN1bHQgPSAodHZkICsgaGVsbGluZ2VyKSAvIDIKICAgIGlmIHJlc3VsdCA+PSB0aHJlc2hvbGQ6CiAgICAgICAgcmV0dXJuIFRydWUsIHJlc3VsdAogICAgcmV0dXJuIEZhbHNlLCByZXN1bHQKCgpkZWYgX3BlcmZvcm1fZHJpZnRfYW5hbHlzaXMoCiAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6IGRpY3QsCiAgICBpbnB1dHM6IHBkLkRhdGFGcmFtZSwKICAgIGRyaWZ0X3RocmVzaG9sZDogZmxvYXQsCiAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IGZsb2F0LAogICAgaW5mX2NhcHBpbmc6IGZsb2F0LAopIC0+IFR1cGxlW0FydGlmYWN0LCBBcnRpZmFjdCwgZGljdF06CiAgICAiIiIKICAgIFBlcmZvcm0gZHJpZnQgYW5hbHlzaXMsIHByb2R1Y2luZyB0aGUgZHJpZnQgdGFibGUgYXJ0aWZhY3QgZm9yIGxvZ2dpbmcgcG9zdCBwcmVkaWN0aW9uLgoKICAgIDpwYXJhbSBzYW1wbGVfc2V0X3N0YXRpc3RpY3M6ICAgIFRoZSBzdGF0aXN0aWNzIG9mIHRoZSBzYW1wbGUgc2V0IGxvZ2dlZCBhbG9uZyBhIG1vZGVsLgogICAgOnBhcmFtIGlucHV0czogICAgICAgICAgICAgICAgICAgSW5wdXQgZGF0YXNldCB0byBwZXJmb3JtIHRoZSBkcmlmdCBjYWxjdWxhdGlvbiBvbi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mCiAgICAgICAgICAgICAgWzBdID0gQW4gTUxSdW4gYXJ0aWZhY3QgaG9sZGluZyB0aGUgSFRNTCBjb2RlIG9mIHRoZSBkcmlmdCB0YWJsZSBwbG90LgogICAgICAgICAgICAgIFsxXSA9IEFuIE1MUnVuIGFydGlmYWN0IGhvbGRpbmcgdGhlIG1ldHJpYyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5LgogICAgICAgICAgICAgIFsyXSA9IFJlc3VsdHMgdG8gbG9nIHRoZSBmaW5hbCBhbmFseXNpcyBvdXRjb21lLgogICAgIiIiCiAgICAjIENhbGN1bGF0ZSB0aGUgaW5wdXQncyBzdGF0aXN0aWNzOgogICAgaW5wdXRzX3N0YXRpc3RpY3MgPSBjYWxjdWxhdGVfaW5wdXRzX3N0YXRpc3RpY3MoCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICBpbnB1dHM9aW5wdXRzLAogICAgKQoKICAgICMgQ2FsY3VsYXRlIGRyaWZ0OgogICAgdmlydHVhbF9kcmlmdCA9IFZpcnR1YWxEcmlmdChpbmZfY2FwcGluZz1pbmZfY2FwcGluZykKICAgIG1ldHJpY3MgPSB2aXJ0dWFsX2RyaWZ0LmNvbXB1dGVfZHJpZnRfZnJvbV9oaXN0b2dyYW1zKAogICAgICAgIGZlYXR1cmVfc3RhdHM9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgIGN1cnJlbnRfc3RhdHM9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICApCiAgICBkcmlmdF9yZXN1bHRzID0gdmlydHVhbF9kcmlmdC5jaGVja19mb3JfZHJpZnRfcGVyX2ZlYXR1cmUoCiAgICAgICAgbWV0cmljc19yZXN1bHRzX2RpY3Rpb25hcnk9bWV0cmljcywKICAgICAgICBwb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ9cG9zc2libGVfZHJpZnRfdGhyZXNob2xkLAogICAgICAgIGRyaWZ0X2RldGVjdGVkX3RocmVzaG9sZD1kcmlmdF90aHJlc2hvbGQsCiAgICApCgogICAgIyBWYWxpZGF0ZSBhbGwgZmVhdHVyZSBjb2x1bW5zIG5hbWVkIHRoZSBzYW1lIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgc2FtcGxlIHNldHM6CiAgICBzYW1wbGVfZmVhdHVyZXMgPSBzZXQoCiAgICAgICAgWwogICAgICAgICAgICBmZWF0dXJlX25hbWUKICAgICAgICAgICAgZm9yIGZlYXR1cmVfbmFtZSwgZmVhdHVyZV9zdGF0aXN0aWNzIGluIHNhbXBsZV9zZXRfc3RhdGlzdGljcy5pdGVtcygpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZmVhdHVyZV9zdGF0aXN0aWNzLCBkaWN0KQogICAgICAgIF0KICAgICkKICAgIGlucHV0X2ZlYXR1cmVzID0gc2V0KGlucHV0cy5jb2x1bW5zKQogICAgaWYgbGVuKHNhbXBsZV9mZWF0dXJlcyAmIGlucHV0X2ZlYXR1cmVzKSAhPSBsZW4oaW5wdXRfZmVhdHVyZXMpOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIk5vdCBhbGwgZmVhdHVyZSBuYW1lcyB3ZXJlIG1hdGNoaW5nIGJldHdlZW4gdGhlIGlucHV0cyBhbmQgdGhlIHNhbXBsZSBzZXQgcHJvdmlkZWQ6ICIKICAgICAgICAgICAgZiJ7aW5wdXRfZmVhdHVyZXMgLSBzYW1wbGVfZmVhdHVyZXMgfCBzYW1wbGVfZmVhdHVyZXMgLSBpbnB1dF9mZWF0dXJlc30iCiAgICAgICAgKQoKICAgICMgUGxvdDoKICAgIGh0bWxfcGxvdCA9IEZlYXR1cmVzRHJpZnRUYWJsZVBsb3QoKS5wcm9kdWNlKAogICAgICAgIGZlYXR1cmVzPWxpc3QoaW5wdXRfZmVhdHVyZXMpLAogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcz1zYW1wbGVfc2V0X3N0YXRpc3RpY3MsCiAgICAgICAgaW5wdXRzX3N0YXRpc3RpY3M9aW5wdXRzX3N0YXRpc3RpY3MsCiAgICAgICAgbWV0cmljcz1tZXRyaWNzLAogICAgICAgIGRyaWZ0X3Jlc3VsdHM9ZHJpZnRfcmVzdWx0cywKICAgICkKCiAgICAjIFByZXBhcmUgbWV0cmljcyBwZXIgZmVhdHVyZSBkaWN0aW9uYXJ5OgogICAgbWV0cmljc19wZXJfZmVhdHVyZSA9IHsKICAgICAgICBmZWF0dXJlOiBfZ2V0X2RyaWZ0X3Jlc3VsdCgKICAgICAgICAgICAgdHZkPW1ldHJpY19kaWN0aW9uYXJ5WyJ0dmQiXSwKICAgICAgICAgICAgaGVsbGluZ2VyPW1ldHJpY19kaWN0aW9uYXJ5WyJoZWxsaW5nZXIiXSwKICAgICAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICApWzFdCiAgICAgICAgZm9yIGZlYXR1cmUsIG1ldHJpY19kaWN0aW9uYXJ5IGluIG1ldHJpY3MuaXRlbXMoKQogICAgICAgIGlmIGlzaW5zdGFuY2UobWV0cmljX2RpY3Rpb25hcnksIGRpY3QpCiAgICB9CgogICAgIyBDYWxjdWxhdGUgdGhlIGZpbmFsIGFuYWx5c2lzIHJlc3VsdDoKICAgIGRyaWZ0X3N0YXR1cywgZHJpZnRfbWV0cmljID0gX2dldF9kcmlmdF9yZXN1bHQoCiAgICAgICAgdHZkPW1ldHJpY3NbInR2ZF9tZWFuIl0sCiAgICAgICAgaGVsbGluZ2VyPW1ldHJpY3NbImhlbGxpbmdlcl9tZWFuIl0sCiAgICAgICAgdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICkKCiAgICByZXR1cm4gKAogICAgICAgIEFydGlmYWN0KGJvZHk9aHRtbF9wbG90LCBmb3JtYXQ9Imh0bWwiLCBrZXk9ImRyaWZ0X3RhYmxlX3Bsb3QiKSwKICAgICAgICBBcnRpZmFjdCgKICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKG1ldHJpY3NfcGVyX2ZlYXR1cmUpLAogICAgICAgICAgICBmb3JtYXQ9Impzb24iLAogICAgICAgICAgICBrZXk9ImZlYXR1cmVzX2RyaWZ0X3Jlc3VsdHMiLAogICAgICAgICksCiAgICAgICAgeyJkcmlmdF9zdGF0dXMiOiBkcmlmdF9zdGF0dXMsICJkcmlmdF9tZXRyaWMiOiBkcmlmdF9tZXRyaWN9LAogICAgKQoKCmRlZiBpbmZlcigKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWw6IHN0ciwKICAgIGRhdGFzZXQ6IERhdGFzZXRUeXBlLAogICAgZHJvcF9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXSwgaW50LCBMaXN0W2ludF1dID0gTm9uZSwKICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBmZWF0dXJlX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICBsb2dfcmVzdWx0X3NldDogYm9vbCA9IFRydWUsCiAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgIGJhdGNoX2lkOiBzdHIgPSBOb25lLAogICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpczogYm9vbCA9IE5vbmUsCiAgICBzYW1wbGVfc2V0OiBEYXRhc2V0VHlwZSA9IE5vbmUsCiAgICBkcmlmdF90aHJlc2hvbGQ6IGZsb2F0ID0gMC43LAogICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIGluZl9jYXBwaW5nOiBmbG9hdCA9IDEwLjAsCiAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAopOgogICAgIiIiCiAgICBQZXJmb3JtIGEgcHJlZGljdGlvbiBvbiBhIGdpdmVuIGRhdGFzZXQgd2l0aCB0aGUgZ2l2ZW4gbW9kZWwuIENhbiBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGJldHdlZW4gdGhlIHNhbXBsZSBzZXQKICAgIHN0YXRpc3RpY3Mgc3RvcmVkIGluIHRoZSBtb2RlbCB0byB0aGUgY3VycmVudCBpbnB1dCBkYXRhLiBUaGUgZHJpZnQgcnVsZSBpcyB0aGUgdmFsdWUgcGVyLWZlYXR1cmUgbWVhbiBvZiB0aGUgVFZECiAgICBhbmQgSGVsbGluZ2VyIHNjb3JlcyBhY2NvcmRpbmcgdG8gdGhlIHRocmVzaG9sZHMgY29uZmlndXJlcyBoZXJlLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgICAgIE1MUnVuIGNvbnRleHQuCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICAgICBUaGUgbW9kZWwgU3RvcmUgcGF0aC4KICAgIDpwYXJhbSBkYXRhc2V0OiAgICAgICAgICAgICAgICAgIFRoZSBkYXRhc2V0IHRvIGluZmVyIHRocm91Z2ggdGhlIG1vZGVsLiBDYW4gYmUgcGFzc2VkIGluIGBpbnB1dHNgIGFzIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLiBPciwgaW4gYHBhcmFtZXRlcnNgIGFzIGEgbGlzdCwgZGljdGlvbmFyeSBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gZHJvcF9jb2x1bW5zOiAgICAgICAgICAgICBBIHN0cmluZyAvIGludGVnZXIgb3IgYSBsaXN0IG9mIHN0cmluZ3MgLyBpbnRlZ2VycyB0aGF0IHJlcHJlc2VudCB0aGUgY29sdW1uIG5hbWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvIGluZGljZXMgdG8gZHJvcC4gV2hlbiB0aGUgZGF0YXNldCBpcyBhIGxpc3Qgb3IgYSBudW1weSBhcnJheSB0aGlzIHBhcmFtZXRlciBtdXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSByZXByZXNlbnRlZCBieSBpbnRlZ2Vycy4KICAgIDpwYXJhbSBsYWJlbF9jb2x1bW5zOiAgICAgICAgICAgIFRoZSB0YXJnZXQgbGFiZWwocykgb2YgdGhlIGNvbHVtbihzKSBpbiB0aGUgZGF0YXNldCBmb3IgUmVncmVzc2lvbiBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3NpZmljYXRpb24gdGFza3MuIFRoZSBsYWJlbCBjb2x1bW4gY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIG1vZGVsIG9iamVjdCwgb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBmZWF0dXJlIHZlY3RvciBwcm92aWRlZCBpZiBhdmFpbGFibGUuCiAgICA6cGFyYW0gZmVhdHVyZV9jb2x1bW5zOiAgICAgICAgICBMaXN0IG9mIGZlYXR1cmUgY29sdW1ucyB0aGF0IHdpbGwgYmUgdXNlZCB0byBidWlsZCB0aGUgZGF0YWZyYW1lIHdoZW4gZGF0YXNldCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbSB0eXBlIGxpc3Qgb3IgbnVtcHkgYXJyYXkuCiAgICA6cGFyYW0gbG9nX3Jlc3VsdF9zZXQ6ICAgICAgICAgICBXaGV0aGVyIHRvIGxvZyB0aGUgcmVzdWx0IHNldCAtIGEgRGF0YUZyYW1lIG9mIHRoZSBnaXZlbiBpbnB1dHMgY29uY2F0ZW5hdGVkIHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBwcmVkaWN0aW9ucy4gRGVmYXVsdGVkIHRvIFRydWUuCiAgICA6cGFyYW0gcmVzdWx0X3NldF9uYW1lOiAgICAgICAgICBUaGUgZGIga2V5IHRvIHNldCBuYW1lIG9mIHRoZSBwcmVkaWN0aW9uIHJlc3VsdCBhbmQgdGhlIGZpbGVuYW1lLiBEZWZhdWx0ZWQgdG8KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9uJy4KICAgIDpwYXJhbSBiYXRjaF9pZDogICAgICAgICAgICAgICAgIFRoZSBJRCBvZiB0aGUgZ2l2ZW4gYmF0Y2ggKGluZmVyZW5jZSBkYXRhc2V0KS4gSWYgYE5vbmVgLCBpdCB3aWxsIGJlIGdlbmVyYXRlZC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdpbGwgYmUgbG9nZ2VkIGFzIGEgcmVzdWx0IG9mIHRoZSBydW4uCiAgICA6cGFyYW0gcGVyZm9ybV9kcmlmdF9hbmFseXNpczogICBXaGV0aGVyIHRvIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgYmV0d2VlbiB0aGUgc2FtcGxlIHNldCBvZiB0aGUgbW9kZWwgb2JqZWN0IHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCBnaXZlbi4gQnkgZGVmYXVsdCwgTm9uZSwgd2hpY2ggbWVhbnMgaXQgd2lsbCBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlmIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgaGFzIGEgc2FtcGxlIHNldCBzdGF0aXN0aWNzLiBQZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIHdpbGwgcHJvZHVjZSBhIGRhdGEgZHJpZnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlIGFydGlmYWN0LgogICAgOnBhcmFtIHNhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuIFRoZSBkZWZhdWx0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSBkcmlmdF90aHJlc2hvbGQ6ICAgICAgICAgIFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjcuCiAgICA6cGFyYW0gcG9zc2libGVfZHJpZnRfdGhyZXNob2xkOiBUaGUgdGhyZXNob2xkIG9mIHdoaWNoIHRvIG1hcmsgcG9zc2libGUgZHJpZnRzLiBEZWZhdWx0ZWQgdG8gMC41LgogICAgOnBhcmFtIGluZl9jYXBwaW5nOiAgICAgICAgICAgICAgVGhlIHZhbHVlIHRvIHNldCBmb3Igd2hlbiBpdCByZWFjaGVkIGluZmluaXR5LiBEZWZhdWx0ZWQgdG8gMTAuMC4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIGFsbCB0aGUgYXJ0aWZhY3RzIHJlc3VsdGVkIGZyb20gdGhlIGZ1bmN0aW9uLgogICAgIiIiCiAgICAjIExvYWRpbmcgdGhlIG1vZGVsOgogICAgY29udGV4dC5sb2dnZXIuaW5mbyhmIkxvYWRpbmcgbW9kZWwuLi4iKQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKG1vZGVsX3BhdGg9bW9kZWwsIGNvbnRleHQ9Y29udGV4dCkKICAgIGlmIGxhYmVsX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBsYWJlbF9jb2x1bW5zID0gWwogICAgICAgICAgICBvdXRwdXQubmFtZSBmb3Igb3V0cHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMub3V0cHV0cwogICAgICAgIF0KCiAgICBpZiBmZWF0dXJlX2NvbHVtbnMgaXMgTm9uZToKICAgICAgICBmZWF0dXJlX2NvbHVtbnMgPSBbCiAgICAgICAgICAgIGlucHV0Lm5hbWUgZm9yIGlucHV0IGluIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuaW5wdXRzCiAgICAgICAgXQoKICAgICMgR2V0IGRhdGFzZXQgYnkgb2JqZWN0LCBVUkwgb3IgYnkgRmVhdHVyZVZlY3RvcjoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIGRhdGEuLi4iKQogICAgeCwgbGFiZWxfY29sdW1ucyA9IF9yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICAjIExvZyB0aGUgcmVzdWx0IHNldDoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9nZ2luZyByZXN1bHQgc2V0ICh4IHwgcHJlZGljdGlvbikuLi4iKQogICAgICAgIGNvbnRleHQubG9nX2RhdGFzZXQoCiAgICAgICAgICAgIGtleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIGRmPXJlc3VsdF9zZXQsCiAgICAgICAgICAgIGRiX2tleT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHRhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICkKICAgICAgICAjIExvZyB0aGUgYmF0Y2ggSUQ6CiAgICAgICAgaWYgYmF0Y2hfaWQgaXMgTm9uZToKICAgICAgICAgICAgYmF0Y2hfaWQgPSBoYXNobGliLnNoYTIyNChzdHIoZGF0ZXRpbWUubm93KCkpLmVuY29kZSgpKS5oZXhkaWdlc3QoKQogICAgICAgIGNvbnRleHQubG9nX3Jlc3VsdCgKICAgICAgICAgICAga2V5PSJiYXRjaF9pZCIsCiAgICAgICAgICAgIHZhbHVlPWJhdGNoX2lkLAogICAgICAgICkKCiAgICAjIENoZWNrIGZvciBwZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzOgogICAgaWYgKAogICAgICAgIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXMgaXMgTm9uZQogICAgICAgIGFuZCBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmZlYXR1cmVfc3RhdHMgaXMgbm90IE5vbmUKICAgICk6CiAgICAgICAgcGVyZm9ybV9kcmlmdF9hbmFseXNpcyA9IFRydWUKICAgIGlmIHBlcmZvcm1fZHJpZnRfYW5hbHlzaXM6CiAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiUGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcy4uLiIpCiAgICAgICAgIyBHZXQgdGhlIHNhbXBsZSBzZXQgc3RhdGlzdGljcyAoZWl0aGVyIGZyb20gdGhlIHNhbXBsZSBzZXQgb3IgZnJvbSB0aGUgc3RhdGlzdGljcyBsb2dnZWQgd2l0aCB0aGUgbW9kZWwpOgogICAgICAgIHNhbXBsZV9zZXRfc3RhdGlzdGljcyA9IF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzKAogICAgICAgICAgICBzYW1wbGVfc2V0PXNhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICkKICAgICAgICAjIFByb2R1Y2UgdGhlIGFydGlmYWN0OgogICAgICAgICgKICAgICAgICAgICAgZHJpZnRfdGFibGVfcGxvdCwKICAgICAgICAgICAgbWV0cmljX3Blcl9mZWF0dXJlX2RpY3QsCiAgICAgICAgICAgIGFuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgKSA9IF9wZXJmb3JtX2RyaWZ0X2FuYWx5c2lzKAogICAgICAgICAgICBzYW1wbGVfc2V0X3N0YXRpc3RpY3M9c2FtcGxlX3NldF9zdGF0aXN0aWNzLAogICAgICAgICAgICBpbnB1dHM9cmVzdWx0X3NldCwKICAgICAgICAgICAgZHJpZnRfdGhyZXNob2xkPWRyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgcG9zc2libGVfZHJpZnRfdGhyZXNob2xkPXBvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZCwKICAgICAgICAgICAgaW5mX2NhcHBpbmc9aW5mX2NhcHBpbmcsCiAgICAgICAgKQogICAgICAgICMgTG9nIHRoZSBhcnRpZmFjdCBhbmQgcmVzdWx0czoKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChkcmlmdF90YWJsZV9wbG90LCB0YWc9YXJ0aWZhY3RzX3RhZykKICAgICAgICBjb250ZXh0LmxvZ19hcnRpZmFjdChtZXRyaWNfcGVyX2ZlYXR1cmVfZGljdCwgdGFnPWFydGlmYWN0c190YWcpCiAgICAgICAgY29udGV4dC5sb2dfcmVzdWx0cyhyZXN1bHRzPWFuYWx5c2lzX3Jlc3VsdHMpCg==
+    origin_filename: ''
+    auto_build: false
+    code_origin: ''
+    with_mlrun: false
   disable_auto_mount: false
-  allow_empty_resources: true
-  clone_target_dir: ''
-  env: []
-  priority_class_name: ''
-  preemption_mode: prevent
-  affinity: null
-  tolerations: null
-  security_context: {}
-verbose: false
+  description: Batch inference (also knows as prediction) for the common ML frameworks
+    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 
         
     
diff --git a/functions/master/batch_inference/latest/static/item.html b/functions/master/batch_inference/latest/static/item.html index a37f5b3f..812ce885 100644 --- a/functions/master/batch_inference/latest/static/item.html +++ b/functions/master/batch_inference/latest/static/item.html @@ -30,7 +30,7 @@ apiVersion: v1 categories: -- utils +- model-serving description: Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. doc: '' @@ -42,7 +42,7 @@ author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.1 +mlrunVersion: 1.7.0 name: batch_inference platformVersion: 3.5.0 spec: @@ -57,7 +57,7 @@ kind: job requirements: url: '' -version: 1.7.0 +version: 1.8.0 diff --git a/functions/master/batch_inference_v2/2.6.0/src/function.yaml b/functions/master/batch_inference_v2/2.6.0/src/function.yaml index e0a9310c..014cb216 100644 --- a/functions/master/batch_inference_v2/2.6.0/src/function.yaml +++ b/functions/master/batch_inference_v2/2.6.0/src/function.yaml @@ -1,19 +1,10 @@ +verbose: false spec: - image: mlrun/mlrun default_handler: infer - command: '' - allow_empty_resources: true - description: Batch inference (also knows as prediction) for the common ML frameworks - (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. - disable_auto_mount: false - build: - with_mlrun: false - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp - code_origin: '' - auto_build: false - origin_filename: '' entry_points: infer: + lineno: 102 + name: infer parameters: - name: context type: MLClientCtx @@ -111,8 +102,7 @@ spec: doc: The threshold of which to mark possible drifts. Defaulted to 0.5. default: null has_kwargs: true - lineno: 102 - name: infer + has_varargs: false doc: 'Perform a prediction on the provided dataset using the specified model. Ensure that the model has already been logged under the current project. @@ -133,13 +123,21 @@ spec: At the moment, this function is supported for `mlrun>=1.5.0` versions.' - has_varargs: false -verbose: false + command: '' + build: + with_mlrun: false + code_origin: '' + origin_filename: '' + auto_build: false + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp + allow_empty_resources: true + disable_auto_mount: false + image: mlrun/mlrun + description: Batch inference (also knows as prediction) for the common ML frameworks + (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. metadata: - name: batch-inference-v2 tag: '' categories: - - utils - - data-analysis - - monitoring + - model-serving + name: batch-inference-v2 kind: job diff --git a/functions/master/batch_inference_v2/2.6.0/src/item.yaml b/functions/master/batch_inference_v2/2.6.0/src/item.yaml index e995c770..775579b9 100644 --- a/functions/master/batch_inference_v2/2.6.0/src/item.yaml +++ b/functions/master/batch_inference_v2/2.6.0/src/item.yaml @@ -1,8 +1,6 @@ apiVersion: v1 categories: -- utils -- data-analysis -- monitoring +- model-serving description: Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis. doc: '' diff --git a/functions/master/batch_inference_v2/2.6.0/static/batch_inference_v2.html b/functions/master/batch_inference_v2/2.6.0/static/batch_inference_v2.html index 9c41518e..85861922 100644 --- a/functions/master/batch_inference_v2/2.6.0/static/batch_inference_v2.html +++ b/functions/master/batch_inference_v2/2.6.0/static/batch_inference_v2.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/batch_inference_v2/2.6.0/static/documentation.html b/functions/master/batch_inference_v2/2.6.0/static/documentation.html index b45d98d7..bb677bf7 100644 --- a/functions/master/batch_inference_v2/2.6.0/static/documentation.html +++ b/functions/master/batch_inference_v2/2.6.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/batch_inference_v2/2.6.0/static/example.html b/functions/master/batch_inference_v2/2.6.0/static/example.html index 5fb0f011..c3bc49bd 100644 --- a/functions/master/batch_inference_v2/2.6.0/static/example.html +++ b/functions/master/batch_inference_v2/2.6.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/batch_inference_v2/2.6.0/static/function.html b/functions/master/batch_inference_v2/2.6.0/static/function.html index 94730d3a..39926fe5 100644 --- a/functions/master/batch_inference_v2/2.6.0/static/function.html +++ b/functions/master/batch_inference_v2/2.6.0/static/function.html @@ -28,22 +28,13 @@
         
+verbose: false
 spec:
-  image: mlrun/mlrun
   default_handler: infer
-  command: ''
-  allow_empty_resources: true
-  description: Batch inference (also knows as prediction) for the common ML frameworks
-    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
-  disable_auto_mount: false
-  build:
-    with_mlrun: false
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
-    code_origin: ''
-    auto_build: false
-    origin_filename: ''
   entry_points:
     infer:
+      lineno: 102
+      name: infer
       parameters:
       - name: context
         type: MLClientCtx
@@ -141,8 +132,7 @@
         doc: The threshold of which to mark possible drifts. Defaulted to 0.5.
         default: null
       has_kwargs: true
-      lineno: 102
-      name: infer
+      has_varargs: false
       doc: 'Perform a prediction on the provided dataset using the specified model.
 
         Ensure that the model has already been logged under the current project.
@@ -163,15 +153,23 @@
 
 
         At the moment, this function is supported for `mlrun>=1.5.0` versions.'
-      has_varargs: false
-verbose: false
+  command: ''
+  build:
+    with_mlrun: false
+    code_origin: ''
+    origin_filename: ''
+    auto_build: false
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
+  allow_empty_resources: true
+  disable_auto_mount: false
+  image: mlrun/mlrun
+  description: Batch inference (also knows as prediction) for the common ML frameworks
+    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 metadata:
-  name: batch-inference-v2
   tag: ''
   categories:
-  - utils
-  - data-analysis
-  - monitoring
+  - model-serving
+  name: batch-inference-v2
 kind: job
 
         
diff --git a/functions/master/batch_inference_v2/2.6.0/static/item.html b/functions/master/batch_inference_v2/2.6.0/static/item.html
index 19b6d7ed..b73b3ff0 100644
--- a/functions/master/batch_inference_v2/2.6.0/static/item.html
+++ b/functions/master/batch_inference_v2/2.6.0/static/item.html
@@ -30,9 +30,7 @@
         
 apiVersion: v1
 categories:
-- utils
-- data-analysis
-- monitoring
+- model-serving
 description: Batch inference (also knows as prediction) for the common ML frameworks
   (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 doc: ''
diff --git a/functions/master/batch_inference_v2/latest/src/function.yaml b/functions/master/batch_inference_v2/latest/src/function.yaml
index e0a9310c..014cb216 100644
--- a/functions/master/batch_inference_v2/latest/src/function.yaml
+++ b/functions/master/batch_inference_v2/latest/src/function.yaml
@@ -1,19 +1,10 @@
+verbose: false
 spec:
-  image: mlrun/mlrun
   default_handler: infer
-  command: ''
-  allow_empty_resources: true
-  description: Batch inference (also knows as prediction) for the common ML frameworks
-    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
-  disable_auto_mount: false
-  build:
-    with_mlrun: false
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
-    code_origin: ''
-    auto_build: false
-    origin_filename: ''
   entry_points:
     infer:
+      lineno: 102
+      name: infer
       parameters:
       - name: context
         type: MLClientCtx
@@ -111,8 +102,7 @@ spec:
         doc: The threshold of which to mark possible drifts. Defaulted to 0.5.
         default: null
       has_kwargs: true
-      lineno: 102
-      name: infer
+      has_varargs: false
       doc: 'Perform a prediction on the provided dataset using the specified model.
 
         Ensure that the model has already been logged under the current project.
@@ -133,13 +123,21 @@ spec:
 
 
         At the moment, this function is supported for `mlrun>=1.5.0` versions.'
-      has_varargs: false
-verbose: false
+  command: ''
+  build:
+    with_mlrun: false
+    code_origin: ''
+    origin_filename: ''
+    auto_build: false
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
+  allow_empty_resources: true
+  disable_auto_mount: false
+  image: mlrun/mlrun
+  description: Batch inference (also knows as prediction) for the common ML frameworks
+    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 metadata:
-  name: batch-inference-v2
   tag: ''
   categories:
-  - utils
-  - data-analysis
-  - monitoring
+  - model-serving
+  name: batch-inference-v2
 kind: job
diff --git a/functions/master/batch_inference_v2/latest/src/item.yaml b/functions/master/batch_inference_v2/latest/src/item.yaml
index e995c770..775579b9 100644
--- a/functions/master/batch_inference_v2/latest/src/item.yaml
+++ b/functions/master/batch_inference_v2/latest/src/item.yaml
@@ -1,8 +1,6 @@
 apiVersion: v1
 categories:
-- utils
-- data-analysis
-- monitoring
+- model-serving
 description: Batch inference (also knows as prediction) for the common ML frameworks
   (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 doc: ''
diff --git a/functions/master/batch_inference_v2/latest/static/batch_inference_v2.html b/functions/master/batch_inference_v2/latest/static/batch_inference_v2.html
index 9c41518e..85861922 100644
--- a/functions/master/batch_inference_v2/latest/static/batch_inference_v2.html
+++ b/functions/master/batch_inference_v2/latest/static/batch_inference_v2.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/batch_inference_v2/latest/static/documentation.html b/functions/master/batch_inference_v2/latest/static/documentation.html
index b45d98d7..bb677bf7 100644
--- a/functions/master/batch_inference_v2/latest/static/documentation.html
+++ b/functions/master/batch_inference_v2/latest/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/batch_inference_v2/latest/static/example.html b/functions/master/batch_inference_v2/latest/static/example.html
index 5fb0f011..c3bc49bd 100644
--- a/functions/master/batch_inference_v2/latest/static/example.html
+++ b/functions/master/batch_inference_v2/latest/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/batch_inference_v2/latest/static/function.html b/functions/master/batch_inference_v2/latest/static/function.html
index 94730d3a..39926fe5 100644
--- a/functions/master/batch_inference_v2/latest/static/function.html
+++ b/functions/master/batch_inference_v2/latest/static/function.html
@@ -28,22 +28,13 @@
 
     
         
+verbose: false
 spec:
-  image: mlrun/mlrun
   default_handler: infer
-  command: ''
-  allow_empty_resources: true
-  description: Batch inference (also knows as prediction) for the common ML frameworks
-    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
-  disable_auto_mount: false
-  build:
-    with_mlrun: false
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
-    code_origin: ''
-    auto_build: false
-    origin_filename: ''
   entry_points:
     infer:
+      lineno: 102
+      name: infer
       parameters:
       - name: context
         type: MLClientCtx
@@ -141,8 +132,7 @@
         doc: The threshold of which to mark possible drifts. Defaulted to 0.5.
         default: null
       has_kwargs: true
-      lineno: 102
-      name: infer
+      has_varargs: false
       doc: 'Perform a prediction on the provided dataset using the specified model.
 
         Ensure that the model has already been logged under the current project.
@@ -163,15 +153,23 @@
 
 
         At the moment, this function is supported for `mlrun>=1.5.0` versions.'
-      has_varargs: false
-verbose: false
+  command: ''
+  build:
+    with_mlrun: false
+    code_origin: ''
+    origin_filename: ''
+    auto_build: false
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgpmcm9tIGluc3BlY3QgaW1wb3J0IHNpZ25hdHVyZQpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBVbmlvbiwgT3B0aW9uYWwKaW1wb3J0IG1scnVuCgp0cnk6CiAgICBpbXBvcnQgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5Ob3RGb3VuZEVycm9yKAogICAgICAgIGYiUGxlYXNlIHVwZGF0ZSB5b3VyIGBtbHJ1bmAgdmVyc2lvbiB0byA+PTEuNS4wIG9yIHVzZSBhbiAiCiAgICAgICAgZiJvbGRlciB2ZXJzaW9uIG9mIHRoZSBiYXRjaCBpbmZlcmVuY2UgZnVuY3Rpb24uIgogICAgKQoKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKCmRlZiBfcHJlcGFyZV9yZXN1bHRfc2V0KHg6IHBkLkRhdGFGcmFtZSwgbGFiZWxfY29sdW1uczogTGlzdFtzdHJdLCB5X3ByZWQ6IG5wLm5kYXJyYXkpIC0+IHBkLkRhdGFGcmFtZToKICAgICIiIgogICAgU2V0IGRlZmF1bHQgbGFiZWwgY29sdW1uIG5hbWVzIGFuZCB2YWxpZGF0ZSBnaXZlbiBuYW1lcyB0byBwcmVwYXJlIHRoZSByZXN1bHQgc2V0IC0gYSBjb25jYXRlbmF0aW9uIG9mIHRoZSBpbnB1dHMKICAgICh4KSBhbmQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICh5X3ByZWQpLgoKICAgIDpwYXJhbSB4OiAgICAgICAgICAgICBUaGUgaW5wdXRzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6IEEgbGlzdCBvZiBzdHJpbmdzIHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGNvbHVtbiBuYW1lcyB0byBhZGQgdG8gdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0IG5hbWUKICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgaW4gY2FzZSB0aGUgbGlzdCBpcyBlbXB0eSAocHJlZGljdGVkX2xhYmVsX3tpfSkuCiAgICA6cGFyYW0geV9wcmVkOiAgICAgICAgVGhlIG1vZGVsIHByZWRpY3Rpb25zIG9uIHRoZSBpbnB1dHMuCgogICAgOnJldHVybnM6IFRoZSByZXN1bHQgc2V0LgoKICAgIHJhaXNlcyBNTFJ1bkludmFsaWRBcmd1bWVudEVycm9yOiBJZiB0aGUgbGFiZWxzIGNvbHVtbnMgYW1vdW50IGRvIG5vdCBtYXRjaCB0aGUgb3V0cHV0cyBvciBpZiBvbmUgb2YgdGhlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbiBhbHJlYWR5IGV4aXN0cyBpbiB0aGUgZGF0YXNldC4KICAgICIiIgogICAgIyBQcmVwYXJlIGRlZmF1bHQgdGFyZ2V0IGNvbHVtbnMgbmFtZXMgaWYgbm90IHByb3ZpZGVkOgogICAgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9IDEgaWYgbGVuKHlfcHJlZC5zaGFwZSkgPT0gMSBlbHNlIHlfcHJlZC5zaGFwZVsxXQogICAgaWYgbGVuKGxhYmVsX2NvbHVtbnMpID09IDA6CiAgICAgICAgIyBBZGQgZGVmYXVsdCBsYWJlbCBjb2x1bW4gbmFtZXM6CiAgICAgICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCA9PSAxOgogICAgICAgICAgICBsYWJlbF9jb2x1bW5zID0gWyJwcmVkaWN0ZWRfbGFiZWwiXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGxhYmVsX2NvbHVtbnMgPSBbCiAgICAgICAgICAgICAgICBmInByZWRpY3RlZF9sYWJlbF97aX0iIGZvciBpIGluIHJhbmdlKHByZWRpY3Rpb25fY29sdW1uc19hbW91bnQpCiAgICAgICAgICAgIF0KCiAgICAjIFZhbGlkYXRlIHRoZSBsYWJlbCBjb2x1bW5zOgogICAgaWYgcHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudCAhPSBsZW4obGFiZWxfY29sdW1ucyk6CiAgICAgICAgIyBObyBlcXVhbGl0eSBiZXR3ZWVuIHByb3ZpZGVkIGxhYmVsIGNvbHVtbiBuYW1lcyBhbmQgb3V0cHV0cyBhbW91bnQ6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiBwcmVkaWN0ZWQgbGFiZWxzOiB7cHJlZGljdGlvbl9jb2x1bW5zX2Ftb3VudH0gIgogICAgICAgICAgICBmImlzIG5vdCBlcXVhbCB0byB0aGUgZ2l2ZW4gbGFiZWwgY29sdW1uczoge2xlbihsYWJlbF9jb2x1bW5zKX0iCiAgICAgICAgKQogICAgY29tbW9uX2xhYmVscyA9IHNldChsYWJlbF9jb2x1bW5zKSAmIHNldCh4LmNvbHVtbnMudG9saXN0KCkpCiAgICBpZiBjb21tb25fbGFiZWxzOgogICAgICAgICMgTGFiZWwgY29sdW1uIGV4aXN0IGluIHRoZSBvcmlnaW5hbCBpbnB1dHM6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIGxhYmVsczoge2NvbW1vbl9sYWJlbHN9IGFyZSBhbHJlYWR5IGV4aXN0ZWQgaW4gdGhlIGdpdmVuIGRhdGFzZXQuIgogICAgICAgICkKCiAgICByZXR1cm4gcGQuY29uY2F0KAogICAgICAgIFt4LCBwZC5EYXRhRnJhbWUoeV9wcmVkLCBjb2x1bW5zPWxhYmVsX2NvbHVtbnMsIGluZGV4PXguaW5kZXgpXSwgYXhpcz0xCiAgICApCgoKZGVmIF9nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzX3BhcmFtZXRlcnMoY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6IFVuaW9uWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfYXJ0aWZhY3RfZmVhdHVyZV9zdGF0czogZGljdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHJvcF9jb2x1bW5zOiBPcHRpb25hbFtMaXN0XSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29sdW1uczogT3B0aW9uYWxbTGlzdF0pIC0+IERpY3Rbc3RyLCBBbnldOgogICAgc3RhdGljc19pbnB1dF9mdWxsX2RpY3QgPSBkaWN0KHNhbXBsZV9zZXQ9bW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9hcnRpZmFjdF9mZWF0dXJlX3N0YXRzPW1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9jb2x1bW5zPWZlYXR1cmVfY29sdW1ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2V0X2Ryb3BfY29sdW1ucz1kcm9wX2NvbHVtbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3NldF9sYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICBnZXRfc2FtcGxlX3N0YXRpY3NfZnVuY3Rpb24gPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5nZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzCiAgICBzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QgPSBzaWduYXR1cmUoZ2V0X3NhbXBsZV9zdGF0aWNzX2Z1bmN0aW9uKS5wYXJhbWV0ZXJzCiAgICAjICBBcyBhIHJlc3VsdCBvZiBjaGFuZ2VzIHRvIGlucHV0IHBhcmFtZXRlcnMgaW4gdGhlIG1scnVuLWdldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3MgZnVuY3Rpb24sCiAgICAjICB3ZSB3aWxsIG5vdyBzZW5kIG9ubHkgdGhlIHBhcmFtZXRlcnMgaXQgZXhwZWN0cy4KICAgIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQgPSB7a2V5OiBzdGF0aWNzX2lucHV0X2Z1bGxfZGljdFtrZXldIGZvciBrZXkgaW4gc3RhdGljc19mdW5jdGlvbl9pbnB1dF9kaWN0fQogICAgaWYgbGVuKHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpICE9IGxlbihzdGF0aWNzX2Z1bmN0aW9uX2lucHV0X2RpY3QpOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoZiJnZXRfc2FtcGxlX3NldF9zdGF0aXN0aWNzIGlzIGluIGFuIG9sZGVyIHZlcnNpb247ICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzb21lIHBhcmFtZXRlcnMgd2lsbCBub3QgYmUgc2VudCB0byB0aGUgZnVuY3Rpb24uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZiIgRXhwZWN0ZWQgaW5wdXQ6IHtsaXN0KHN0YXRpY3NfZnVuY3Rpb25faW5wdXRfZGljdC5rZXlzKCkpfSwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBhY3R1YWwgaW5wdXQ6IHtsaXN0KHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQua2V5cygpKX0iKQogICAgcmV0dXJuIHN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQKCgpkZWYgaW5mZXIoCiAgICAgICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICAgICAgZGF0YXNldDogVW5pb25bbWxydW4uRGF0YUl0ZW0sIGxpc3QsIGRpY3QsIHBkLkRhdGFGcmFtZSwgcGQuU2VyaWVzLCBucC5uZGFycmF5XSwKICAgICAgICBtb2RlbF9wYXRoOiBVbmlvbltzdHIsIG1scnVuLkRhdGFJdGVtXSwKICAgICAgICBkcm9wX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdLCBpbnQsIExpc3RbaW50XV0gPSBOb25lLAogICAgICAgIGxhYmVsX2NvbHVtbnM6IFVuaW9uW3N0ciwgTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgZmVhdHVyZV9jb2x1bW5zOiBVbmlvbltzdHIsIExpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgIGxvZ19yZXN1bHRfc2V0OiBib29sID0gVHJ1ZSwKICAgICAgICByZXN1bHRfc2V0X25hbWU6IHN0ciA9ICJwcmVkaWN0aW9uIiwKICAgICAgICBiYXRjaF9pZDogc3RyID0gTm9uZSwKICAgICAgICBhcnRpZmFjdHNfdGFnOiBzdHIgPSAiIiwKICAgICAgICAjIERyaWZ0IGFuYWx5c2lzIHBhcmFtZXRlcnMKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiBib29sID0gTm9uZSwKICAgICAgICBlbmRwb2ludF9pZDogc3RyID0gIiIsCiAgICAgICAgIyBUaGUgZm9sbG93aW5nIG1vZGVsIGVuZHBvaW50IHBhcmFtZXRlcnMgYXJlIHJlbGV2YW50IG9ubHkgaWY6CiAgICAgICAgIyBwZXJmb3JtIGRyaWZ0IGFuYWx5c2lzIGlzIG5vdCBkaXNhYmxlZAogICAgICAgICMgYSBuZXcgbW9kZWwgZW5kcG9pbnQgcmVjb3JkIGlzIGdvaW5nIHRvIGJlIGdlbmVyYXRlZAogICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU6IHN0ciA9ICJiYXRjaC1pbmZlciIsCiAgICAgICAgbW9kZWxfZW5kcG9pbnRfc2FtcGxlX3NldDogVW5pb25bCiAgICAgICAgICAgIG1scnVuLkRhdGFJdGVtLCBsaXN0LCBkaWN0LCBwZC5EYXRhRnJhbWUsIHBkLlNlcmllcywgbnAubmRhcnJheQogICAgICAgIF0gPSBOb25lLAoKICAgICAgICAjIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBhcmUgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkCiAgICAgICAgIyBUT0RPOiBSZW1vdmUgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIG9uY2UgRkhVQi0xMyBpcyByZXNvbHZlZAogICAgICAgIHRyaWdnZXJfbW9uaXRvcmluZ19qb2I6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgICAgICBiYXRjaF9pbWFnZV9qb2I6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZSwKICAgICAgICBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCgogICAgICAgICMgcHJlZGljdGlvbiBrd2FyZ3MgdG8gcGFzcyB0byB0aGUgbW9kZWwgcHJlZGljdCBmdW5jdGlvbgogICAgICAgICoqcHJlZGljdF9rd2FyZ3M6IERpY3Rbc3RyLCBBbnldLAoKKToKICAgICIiIgogICAgUGVyZm9ybSBhIHByZWRpY3Rpb24gb24gdGhlIHByb3ZpZGVkIGRhdGFzZXQgdXNpbmcgdGhlIHNwZWNpZmllZCBtb2RlbC4KICAgIEVuc3VyZSB0aGF0IHRoZSBtb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGxvZ2dlZCB1bmRlciB0aGUgY3VycmVudCBwcm9qZWN0LgoKICAgIElmIHlvdSB3aXNoIHRvIGFwcGx5IG1vbml0b3JpbmcgdG9vbHMgKGUuZy4sIGRyaWZ0IGFuYWx5c2lzKSwgc2V0IHRoZSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIHBhcmFtZXRlciB0byBUcnVlLgogICAgVGhpcyB3aWxsIGNyZWF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQgdW5kZXIgdGhlIHNwZWNpZmllZCBtb2RlbF9lbmRwb2ludF9uYW1lLgogICAgQWRkaXRpb25hbGx5LCBlbnN1cmUgdGhhdCBtb2RlbCBtb25pdG9yaW5nIGlzIGVuYWJsZWQgYXQgdGhlIHByb2plY3QgbGV2ZWwgYnkgY2FsbGluZyB0aGUKICAgIHByb2plY3QuZW5hYmxlX21vZGVsX21vbml0b3JpbmcoKSBmdW5jdGlvbi4gWW91IGNhbiBhbHNvIGFwcGx5IG1vbml0b3JpbmcgdG8gYW4gZXhpc3RpbmcgbW9kZWwgYnkgcHJvdmlkaW5nIGl0cwogICAgZW5kcG9pbnQgaWQgb3IgbmFtZSwgYW5kIHRoZSBtb25pdG9yaW5nIHRvb2xzIHdpbGwgYmUgYXBwbGllZCB0byB0aGF0IGVuZHBvaW50LgoKICAgIEF0IHRoZSBtb21lbnQsIHRoaXMgZnVuY3Rpb24gaXMgc3VwcG9ydGVkIGZvciBgbWxydW4+PTEuNS4wYCB2ZXJzaW9ucy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNTFJ1biBjb250ZXh0LgogICAgOnBhcmFtIGRhdGFzZXQ6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIGRhdGFzZXQgdG8gaW5mZXIgdGhyb3VnaCB0aGUgbW9kZWwuIFByb3ZpZGVkIGFzIGFuIGlucHV0IChEYXRhSXRlbSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoYXQgcmVwcmVzZW50cyBEYXRhc2V0IGFydGlmYWN0IC8gRmVhdHVyZSB2ZWN0b3IgVVJJLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgZGF0YXNldGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBsaXN0LCBkaWN0aW9uYXJ5IG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1weSBhcnJheS4KICAgIDpwYXJhbSBtb2RlbF9wYXRoOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsIHN0b3JlIHVyaSAoc2hvdWxkIHN0YXJ0IHdpdGggc3RvcmU6Ly8pLiBQcm92aWRlZCBhcyBhbiBpbnB1dCAoRGF0YUl0ZW0pLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgdXNpbmcgTUxSdW4gU0RLLCBgbW9kZWxfcGF0aGAgY2FuIGFsc28gYmUgcHJvdmlkZWQgYXMgYSBwYXJhbWV0ZXIgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBnZW5lcmF0ZSBhIHZhbGlkIG1vZGVsIHN0b3JlIFVSSSwgcGxlYXNlIGxvZyB0aGUgbW9kZWwgYmVmb3JlIHJ1bm5pbmcgdGhpcyBmdW5jdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIGBlbmRwb2ludF9pZGAgb2YgZXhpc3RpbmcgbW9kZWwgZW5kcG9pbnQgaXMgcHJvdmlkZWQsIG1ha2Ugc3VyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhhdCBpdCBoYXMgYSBzaW1pbGFyIG1vZGVsIHN0b3JlIHBhdGgsIG90aGVyd2lzZSB0aGUgZHJpZnQgYW5hbHlzaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvbid0IGJlIHRyaWdnZXJlZC4KICAgIDpwYXJhbSBkcm9wX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIC8gaW50ZWdlciBvciBhIGxpc3Qgb2Ygc3RyaW5ncyAvIGludGVnZXJzIHRoYXQgcmVwcmVzZW50IHRoZSBjb2x1bW4gbmFtZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gaW5kaWNlcyB0byBkcm9wLiBXaGVuIHRoZSBkYXRhc2V0IGlzIGEgbGlzdCBvciBhIG51bXB5IGFycmF5IHRoaXMgcGFyYW1ldGVyIG11c3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHJlcHJlc2VudGVkIGJ5IGludGVnZXJzLgogICAgOnBhcmFtIGxhYmVsX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRhcmdldCBsYWJlbChzKSBvZiB0aGUgY29sdW1uKHMpIGluIHRoZSBkYXRhc2V0IGZvciBSZWdyZXNzaW9uIG9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzc2lmaWNhdGlvbiB0YXNrcy4gVGhlIGxhYmVsIGNvbHVtbiBjYW4gYmUgYWNjZXNzZWQgZnJvbSB0aGUgbW9kZWwgb2JqZWN0LCBvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIGZlYXR1cmUgdmVjdG9yIHByb3ZpZGVkIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBmZWF0dXJlX2NvbHVtbnM6ICAgICAgICAgICAgICAgICAgICAgICAgIExpc3Qgb2YgZmVhdHVyZSBjb2x1bW5zIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIHRoZSBkYXRhZnJhbWUgd2hlbiBkYXRhc2V0IGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHR5cGUgbGlzdCBvciBudW1weSBhcnJheS4KICAgIDpwYXJhbSBsb2dfcmVzdWx0X3NldDogICAgICAgICAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gbG9nIHRoZSByZXN1bHQgc2V0IC0gYSBEYXRhRnJhbWUgb2YgdGhlIGdpdmVuIGlucHV0cyBjb25jYXRlbmF0ZWQgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHByZWRpY3Rpb25zLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSByZXN1bHRfc2V0X25hbWU6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBkYiBrZXkgdG8gc2V0IG5hbWUgb2YgdGhlIHByZWRpY3Rpb24gcmVzdWx0IGFuZCB0aGUgZmlsZW5hbWUuIERlZmF1bHRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3ByZWRpY3Rpb24nLgogICAgOnBhcmFtIGJhdGNoX2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIElEIG9mIHRoZSBnaXZlbiBiYXRjaCAoaW5mZXJlbmNlIGRhdGFzZXQpLiBJZiBgTm9uZWAsIGl0IHdpbGwgYmUgZ2VuZXJhdGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV2lsbCBiZSBsb2dnZWQgYXMgYSByZXN1bHQgb2YgdGhlIHJ1bi4KICAgIDpwYXJhbSBhcnRpZmFjdHNfdGFnOiAgICAgICAgICAgICAgICAgICAgICAgICAgIFRhZyB0byB1c2UgZm9yIHByZWRpY3Rpb24gc2V0IHJlc3VsdCBhcnRpZmFjdC4KICAgIDpwYXJhbSBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gcGVyZm9ybSBkcmlmdCBhbmFseXNpcyBiZXR3ZWVuIHRoZSBzYW1wbGUgc2V0IG9mIHRoZSBtb2RlbCBvYmplY3QgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0IGdpdmVuLiBCeSBkZWZhdWx0LCBOb25lLCB3aGljaCBtZWFucyBpdCB3aWxsIHBlcmZvcm0gZHJpZnQgYW5hbHlzaXMgaWYgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBhbHJlYWR5IGhhcyBmZWF0dXJlIHN0YXRzIHRoYXQgYXJlIGNvbnNpZGVyZWQgYXMgYSByZWZlcmVuY2Ugc2FtcGxlIHNldC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1pbmcgZHJpZnQgYW5hbHlzaXMgb24gYSBuZXcgZW5kcG9pbnQgaWQgd2lsbCBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjb3JkLgogICAgOnBhcmFtIGVuZHBvaW50X2lkOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTW9kZWwgZW5kcG9pbnQgdW5pcXVlIElELiBJZiBgcGVyZm9ybV9kcmlmdF9hbmFseXNpc2Agd2FzIHNldCwgdGhlIGVuZHBvaW50X2lkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgZWl0aGVyIHRvIHBlcmZvcm0gdGhlIGFuYWx5c2lzIG9uIGV4aXN0aW5nIG1vZGVsIGVuZHBvaW50IG9yIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZSBhIG5ldyBtb2RlbCBlbmRwb2ludCByZWNvcmQuCiAgICA6cGFyYW0gbW9kZWxfZW5kcG9pbnRfbmFtZTogICAgICAgICAgICAgICAgICAgICBJZiBhIG5ldyBtb2RlbCBlbmRwb2ludCBpcyBnZW5lcmF0ZWQsIHRoZSBtb2RlbCBuYW1lIHdpbGwgYmUgcHJlc2VudGVkIHVuZGVyIHRoaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZHBvaW50LgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQ6ICAgICAgICAgICAgICAgQSBzYW1wbGUgZGF0YXNldCB0byBnaXZlIHRvIGNvbXBhcmUgdGhlIGlucHV0cyBpbiB0aGUgZHJpZnQgYW5hbHlzaXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYW4gYmUgcHJvdmlkZWQgYXMgYW4gaW5wdXQgKERhdGFJdGVtKSBvciBhcyBhIHBhcmFtZXRlciAoZS5nLiBzdHJpbmcsIGxpc3QsIERhdGFGcmFtZSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUaGUgZGVmYXVsdCBjaG9zZW4gc2FtcGxlIHNldCB3aWxsIGFsd2F5cyBiZSB0aGUgb25lIHdobyBpcyBzZXQgaW4gdGhlIG1vZGVsIGFydGlmYWN0IGl0c2VsZi4KICAgIDpwYXJhbSB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOiAgICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdHJpZ2dlciB0aGUgYmF0Y2ggZHJpZnQgYW5hbHlzaXMgYWZ0ZXIgdGhlIGluZmVyIGpvYi4KICAgIDpwYXJhbSBiYXRjaF9pbWFnZV9qb2I6ICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBpbWFnZSB0aGF0IHdpbGwgYmUgdXNlZCB0byByZWdpc3RlciB0aGUgbW9uaXRvcmluZyBiYXRjaCBqb2IgaWYgbm90IGV4aXN0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnkgZGVmYXVsdCwgdGhlIGltYWdlIGlzIG1scnVuL21scnVuLgogICAgOnBhcmFtIG1vZGVsX2VuZHBvaW50X2RyaWZ0X3RocmVzaG9sZDogICAgICAgICAgVGhlIHRocmVzaG9sZCBvZiB3aGljaCB0byBtYXJrIGRyaWZ0cy4gRGVmYXVsdGVkIHRvIDAuNy4KICAgIDpwYXJhbSBtb2RlbF9lbmRwb2ludF9wb3NzaWJsZV9kcmlmdF90aHJlc2hvbGQ6IFRoZSB0aHJlc2hvbGQgb2Ygd2hpY2ggdG8gbWFyayBwb3NzaWJsZSBkcmlmdHMuIERlZmF1bHRlZCB0byAwLjUuCgogICAgcmFpc2VzIE1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3I6IGlmIGJvdGggYG1vZGVsX3BhdGhgIGFuZCBgZW5kcG9pbnRfaWRgIGFyZSBub3QgcHJvdmlkZWQKICAgICIiIgoKCiAgICBpZiB0cmlnZ2VyX21vbml0b3Jpbmdfam9iOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgdHJpZ2dlcl9tb25pdG9yaW5nX2pvYmAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCiAgICBpZiBiYXRjaF9pbWFnZV9qb2I6CiAgICAgICAgY29udGV4dC5sb2dnZXIud2FybmluZygiVGhlIGBiYXRjaF9pbWFnZV9qb2JgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIGJlIHJlbW92ZWQgb25jZSB0aGUgdmVyc2lvbmluZyBtZWNoYW5pc20gaXMgaW1wbGVtZW50ZWQuICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpZiB5b3UgYXJlIHVzaW5nIG1scnVuPDEuNy4wLCBwbGVhc2UgaW1wb3J0IHRoZSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGZvciBleGFtcGxlICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICInaHViOi8vYmF0Y2hfaW5mZXJlbmNlX3YyOjIuNS4wJy4iKQogICAgaWYgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkOgogICAgICAgIGNvbnRleHQubG9nZ2VyLndhcm5pbmcoIlRoZSBgbW9kZWxfZW5kcG9pbnRfZHJpZnRfdGhyZXNob2xkYCBwYXJhbWV0ZXIgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBiZSByZW1vdmVkIG9uY2UgdGhlIHZlcnNpb25pbmcgbWVjaGFuaXNtIGlzIGltcGxlbWVudGVkLiAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWYgeW91IGFyZSB1c2luZyBtbHJ1bjwxLjcuMCwgcGxlYXNlIGltcG9ydCB0aGUgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIGZ1bmN0aW9uLCBmb3IgZXhhbXBsZSAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJ2h1YjovL2JhdGNoX2luZmVyZW5jZV92MjoyLjUuMCcuIikKICAgIGlmIG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZDoKICAgICAgICBjb250ZXh0LmxvZ2dlci53YXJuaW5nKCJUaGUgYG1vZGVsX2VuZHBvaW50X3Bvc3NpYmxlX2RyaWZ0X3RocmVzaG9sZGAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgYmUgcmVtb3ZlZCBvbmNlIHRoZSB2ZXJzaW9uaW5nIG1lY2hhbmlzbSBpcyBpbXBsZW1lbnRlZC4gIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlmIHlvdSBhcmUgdXNpbmcgbWxydW48MS43LjAsIHBsZWFzZSBpbXBvcnQgdGhlIHByZXZpb3VzIHZlcnNpb24gb2YgdGhpcyBmdW5jdGlvbiwgZm9yIGV4YW1wbGUgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIidodWI6Ly9iYXRjaF9pbmZlcmVuY2VfdjI6Mi41LjAnLiIpCgogICAgIyBMb2FkaW5nIHRoZSBtb2RlbDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJMb2FkaW5nIG1vZGVsLi4uIikKICAgIGlmIGlzaW5zdGFuY2UobW9kZWxfcGF0aCwgbWxydW4uRGF0YUl0ZW0pOgogICAgICAgIG1vZGVsX3BhdGggPSBtb2RlbF9wYXRoLmFydGlmYWN0X3VybAogICAgaWYgbm90IG1scnVuLmRhdGFzdG9yZS5pc19zdG9yZV91cmkobW9kZWxfcGF0aCk6CiAgICAgICAgcmFpc2UgbWxydW4uZXJyb3JzLk1MUnVuSW52YWxpZEFyZ3VtZW50RXJyb3IoCiAgICAgICAgICAgIGYiVGhlIHByb3ZpZGVkIG1vZGVsIHBhdGggKHttb2RlbF9wYXRofSkgaXMgaW52YWxpZCAtIHNob3VsZCBzdGFydCB3aXRoIGBzdG9yZTovL2AuICIKICAgICAgICAgICAgZiJQbGVhc2UgbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgbG9nZ2VkIHRoZSBtb2RlbCB1c2luZyBgcHJvamVjdC5sb2dfbW9kZWwoKWAgIgogICAgICAgICAgICBmIndoaWNoIGdlbmVyYXRlcyBhIHVuaXF1ZSBzdG9yZSB1cmkgZm9yIHRoZSBsb2dnZWQgbW9kZWwuIgogICAgICAgICkKICAgIG1vZGVsX2hhbmRsZXIgPSBBdXRvTUxSdW4ubG9hZF9tb2RlbChtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCkKCiAgICBpZiBsYWJlbF9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgbGFiZWxfY29sdW1ucyA9IFsKICAgICAgICAgICAgb3V0cHV0Lm5hbWUgZm9yIG91dHB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLm91dHB1dHMKICAgICAgICBdCgogICAgaWYgZmVhdHVyZV9jb2x1bW5zIGlzIE5vbmU6CiAgICAgICAgZmVhdHVyZV9jb2x1bW5zID0gWwogICAgICAgICAgICBpbnB1dC5uYW1lIGZvciBpbnB1dCBpbiBtb2RlbF9oYW5kbGVyLl9tb2RlbF9hcnRpZmFjdC5zcGVjLmlucHV0cwogICAgICAgIF0KCiAgICAjIEdldCBkYXRhc2V0IGJ5IG9iamVjdCwgVVJMIG9yIGJ5IEZlYXR1cmVWZWN0b3I6CiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYiTG9hZGluZyBkYXRhLi4uIikKICAgIHgsIGxhYmVsX2NvbHVtbnMgPSBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5yZWFkX2RhdGFzZXRfYXNfZGF0YWZyYW1lKAogICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwKICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgIGxhYmVsX2NvbHVtbnM9bGFiZWxfY29sdW1ucywKICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgKQoKICAgICMgUHJlZGljdDoKICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZiJDYWxjdWxhdGluZyBwcmVkaWN0aW9uLi4uIikKICAgIHlfcHJlZCA9IG1vZGVsX2hhbmRsZXIubW9kZWwucHJlZGljdCh4LCAqKnByZWRpY3Rfa3dhcmdzKQoKICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0IHNldDoKICAgIHJlc3VsdF9zZXQgPSBfcHJlcGFyZV9yZXN1bHRfc2V0KHg9eCwgbGFiZWxfY29sdW1ucz1sYWJlbF9jb2x1bW5zLCB5X3ByZWQ9eV9wcmVkKQoKICAgICMgQ2hlY2sgZm9yIGxvZ2dpbmcgdGhlIHJlc3VsdCBzZXQ6CiAgICBpZiBsb2dfcmVzdWx0X3NldDoKICAgICAgICBtbHJ1bi5tb2RlbF9tb25pdG9yaW5nLmFwaS5sb2dfcmVzdWx0KAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIHJlc3VsdF9zZXRfbmFtZT1yZXN1bHRfc2V0X25hbWUsCiAgICAgICAgICAgIHJlc3VsdF9zZXQ9cmVzdWx0X3NldCwKICAgICAgICAgICAgYXJ0aWZhY3RzX3RhZz1hcnRpZmFjdHNfdGFnLAogICAgICAgICAgICBiYXRjaF9pZD1iYXRjaF9pZCwKICAgICAgICApCgogICAgIyBDaGVjayBmb3IgcGVyZm9ybWluZyBkcmlmdCBhbmFseXNpcwogICAgaWYgKAogICAgICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzIGlzIE5vbmUKICAgICAgICAgICAgYW5kIG1vZGVsX2hhbmRsZXIuX21vZGVsX2FydGlmYWN0LnNwZWMuZmVhdHVyZV9zdGF0cyBpcyBub3QgTm9uZQogICAgKToKICAgICAgICBwZXJmb3JtX2RyaWZ0X2FuYWx5c2lzID0gVHJ1ZQogICAgaWYgcGVyZm9ybV9kcmlmdF9hbmFseXNpczoKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJQZXJmb3JtaW5nIGRyaWZ0IGFuYWx5c2lzLi4uIikKICAgICAgICAjIEdldCB0aGUgc2FtcGxlIHNldCBzdGF0aXN0aWNzIChlaXRoZXIgZnJvbSB0aGUgc2FtcGxlIHNldCBvciBmcm9tIHRoZSBzdGF0aXN0aWNzIGxvZ2dlZCB3aXRoIHRoZSBtb2RlbCkKICAgICAgICBzdGF0aXN0aWNzX2lucHV0X2ZpbHRlcmVkID0gX2dldF9zYW1wbGVfc2V0X3N0YXRpc3RpY3NfcGFyYW1ldGVycygKICAgICAgICAgICAgY29udGV4dD1jb250ZXh0LAogICAgICAgICAgICBtb2RlbF9lbmRwb2ludF9zYW1wbGVfc2V0PW1vZGVsX2VuZHBvaW50X3NhbXBsZV9zZXQsCiAgICAgICAgICAgIG1vZGVsX2FydGlmYWN0X2ZlYXR1cmVfc3RhdHM9bW9kZWxfaGFuZGxlci5fbW9kZWxfYXJ0aWZhY3Quc3BlYy5mZWF0dXJlX3N0YXRzLAogICAgICAgICAgICBmZWF0dXJlX2NvbHVtbnM9ZmVhdHVyZV9jb2x1bW5zLAogICAgICAgICAgICBkcm9wX2NvbHVtbnM9ZHJvcF9jb2x1bW5zLAogICAgICAgICAgICBsYWJlbF9jb2x1bW5zPWxhYmVsX2NvbHVtbnMpCiAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzID0gbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkuZ2V0X3NhbXBsZV9zZXRfc3RhdGlzdGljcygqKnN0YXRpc3RpY3NfaW5wdXRfZmlsdGVyZWQpCiAgICAgICAgbWxydW4ubW9kZWxfbW9uaXRvcmluZy5hcGkucmVjb3JkX3Jlc3VsdHMoCiAgICAgICAgICAgIHByb2plY3Q9Y29udGV4dC5wcm9qZWN0LAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIGVuZHBvaW50X2lkPWVuZHBvaW50X2lkLAogICAgICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsCiAgICAgICAgICAgIG1vZGVsX2VuZHBvaW50X25hbWU9bW9kZWxfZW5kcG9pbnRfbmFtZSwKICAgICAgICAgICAgaW5mZXJfcmVzdWx0c19kZj1yZXN1bHRfc2V0LmNvcHkoKSwKICAgICAgICAgICAgc2FtcGxlX3NldF9zdGF0aXN0aWNzPXNhbXBsZV9zZXRfc3RhdGlzdGljcywKICAgICAgICAp
+  allow_empty_resources: true
+  disable_auto_mount: false
+  image: mlrun/mlrun
+  description: Batch inference (also knows as prediction) for the common ML frameworks
+    (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 metadata:
-  name: batch-inference-v2
   tag: ''
   categories:
-  - utils
-  - data-analysis
-  - monitoring
+  - model-serving
+  name: batch-inference-v2
 kind: job
 
         
diff --git a/functions/master/batch_inference_v2/latest/static/item.html b/functions/master/batch_inference_v2/latest/static/item.html
index 19b6d7ed..b73b3ff0 100644
--- a/functions/master/batch_inference_v2/latest/static/item.html
+++ b/functions/master/batch_inference_v2/latest/static/item.html
@@ -30,9 +30,7 @@
         
 apiVersion: v1
 categories:
-- utils
-- data-analysis
-- monitoring
+- model-serving
 description: Batch inference (also knows as prediction) for the common ML frameworks
   (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.
 doc: ''
diff --git a/functions/master/catalog.json b/functions/master/catalog.json
index 38d7b0a5..2ad2c475 100644
--- a/functions/master/catalog.json
+++ b/functions/master/catalog.json
@@ -1 +1 @@
-{"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.7": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.7", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.5", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/open_archive.ipynb", "source": "src/open_archive.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/open_archive.ipynb", "source": "src/open_archive.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/onnx_utils.ipynb", "source": "src/onnx_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/onnx_utils.ipynb", "source": "src/onnx_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1", "assets": {"example": "src/arc_to_parquet.ipynb", "source": "src/arc_to_parquet.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1", "assets": {"example": "src/arc_to_parquet.ipynb", "source": "src/arc_to_parquet.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_serving.ipynb", "source": "src/azureml_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_serving.ipynb", "source": "src/azureml_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.4.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.3.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.1", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["huggingface", "genai", "model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false, "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["huggingface", "genai", "model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false, "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.1.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true, "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false, "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.0.1", "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.4.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.4.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/mlflow_utils.ipynb", "source": "src/mlflow_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/mlflow_utils.ipynb", "source": "src/mlflow_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/noise_reduction.ipynb", "source": "src/noise_reduction.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/noise_reduction.ipynb", "source": "src/noise_reduction.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}}
\ No newline at end of file
+{"tf2_serving": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "tf2-serving", "platformVersion": "3.5.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "tf2-serving", "platformVersion": "", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.0.1", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.9.1", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "tf2 image classification server", "doc": "", "example": "tf2_serving.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "tf2-serving", "platformVersion": "3.2.0", "spec": {"filename": "tf2_serving.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": ["requests", "pillow", "tensorflow>=2.1"]}, "url": "", "version": "0.8.0", "assets": {"example": "src/tf2_serving.ipynb", "source": "src/tf2_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "feature_selection": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "feature-selection", "platformVersion": "2.10.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.9.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc40", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "feature-selection", "platformVersion": "3.2.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection/feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "feature-selection", "platformVersion": "3.5.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.1", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Select features through multiple Statistical and Model filters", "doc": "", "example": "feature_selection.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "orz"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.4", "name": "feature-selection", "platformVersion": "3.6.0", "spec": {"filename": "feature_selection.py", "handler": "feature_selection", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/feature_selection.ipynb", "source": "src/feature_selection.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server", "platformVersion": "3.5.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server", "platformVersion": "", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server", "platformVersion": "3.2.0", "spec": {"filename": "model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "nuclio:serving", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/model_server.ipynb", "source": "src/model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "describe": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.2": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-26:10-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.2", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "describe", "platformVersion": "3.5.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.5.4", "name": "describe", "platformVersion": "2.10.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-04-07:14-20", "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Iguazio"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe", "platformVersion": "3.2.0", "spec": {"filename": "describe.py", "handler": "summarize", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "describe and visualizes dataset stats", "doc": "", "example": "describe.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Davids"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "describe", "platformVersion": "3.5.3", "spec": {"filename": "describe.py", "handler": "analyze", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/describe.ipynb", "source": "src/describe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "github_utils": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "github-utils", "platformVersion": "3.5.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "github-utils", "platformVersion": "", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "add comments to github pull request", "doc": "", "example": "github_utils.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "github-utils", "platformVersion": "3.2.0", "spec": {"filename": "github_utils.py", "handler": "run_summary_comment", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/github_utils.ipynb", "source": "src/github_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "aggregate": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-05-19:22-31", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "aggregate", "platformVersion": "3.0.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "aggregate", "platformVersion": "3.5.2", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "aggregate", "platformVersion": "3.2.0", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Rolling aggregation over Metrics and Lables according to specifications", "doc": "", "example": "aggregate.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avia"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "aggregate", "platformVersion": "3.5.4", "spec": {"filename": "aggregate.py", "handler": "aggregate", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/aggregate.ipynb", "source": "src/aggregate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "load_dataset": {"latest": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "load-dataset", "platformVersion": "3.5.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "load-dataset", "platformVersion": "", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.0", "name": "load-dataset", "platformVersion": "3.5.5", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "load a toy dataset from scikit-learn", "doc": "README.md", "example": "load_dataset.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yjb", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "load-dataset", "platformVersion": "3.2.0", "spec": {"filename": "load_dataset.py", "handler": "load_dataset", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/load_dataset.ipynb", "source": "src/load_dataset.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "auto_trainer": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.6": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.6", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.3": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.3", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.2": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-02-06:10-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.10.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "", "kind": "job", "requirements": []}, "url": "", "version": "0.10.2", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.6.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.7.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.7.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.7": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.7", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.3.0", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-04-26:10-43", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.0.0", "name": "auto_trainer", "platformVersion": "", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.5", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Automatic train, evaluate and predict functions for the ML frameworks - Scikit-Learn, XGBoost and LightGBM.", "doc": "", "example": "auto_trainer.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "auto_trainer", "platformVersion": "3.5.0", "spec": {"filename": "auto_trainer.py", "handler": "train", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/auto_trainer.ipynb", "source": "src/auto_trainer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "v2_model_server": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-server", "platformVersion": "", "spec": {"filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": [], "customFields": {"default_class": "ClassifierModel"}}, "url": "", "version": "0.0.1", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-server", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "generic sklearn model server", "doc": "", "example": "v2_model_server.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh", "framework": "sklearn"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-server", "platformVersion": "3.2.0", "spec": {"customFields": {"default_class": "ClassifierModel"}, "filename": "v2_model_server.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/v2_model_server.ipynb", "source": "src/v2_model_server.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "model_server_tester": {"latest": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "model-server-tester", "platformVersion": "3.5.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "model-server-tester", "platformVersion": "", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.0.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["monitoring", "model-serving"], "description": "test model servers", "doc": "", "example": "model_server_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "model-server-tester", "platformVersion": "3.2.0", "spec": {"filename": "model_server_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/model_server_tester.ipynb", "source": "src/model_server_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "v2_model_tester": {"latest": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "v2-model-tester", "platformVersion": "3.5.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "v2-model-tester", "platformVersion": "", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["model-testing", "machine-learning"], "description": "test v2 model servers", "doc": "", "example": "v2_model_tester.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "v2-model-tester", "platformVersion": "3.2.0", "spec": {"filename": "v2_model_tester.py", "handler": "model_server_tester", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/v2_model_tester.ipynb", "source": "src/v2_model_tester.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "open_archive": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/open_archive.ipynb", "source": "src/open_archive.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Open a file/object archive into a target directory", "doc": "", "example": "open_archive.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yaronh"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0-rc50", "name": "open-archive", "platformVersion": "3.5.0", "spec": {"filename": "open_archive.py", "handler": "open_archive", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/open_archive.ipynb", "source": "src/open_archive.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "onnx_utils": {"latest": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/onnx_utils.ipynb", "source": "src/onnx_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["utils", "deep-learning"], "description": "ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun.", "doc": "", "example": "onnx_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.2", "name": "onnx_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "onnx_utils.py", "handler": "to_onnx", "image": "mlrun/mlrun", "kind": "job", "requirements": ["tqdm~=4.67.1", "tensorflow~=2.19.0", "tf_keras~=2.19.0", "torch~=2.6.0", "torchvision~=0.21.0", "onnx~=1.17.0", "onnxruntime~=1.19.2", "onnxoptimizer~=0.3.13", "onnxmltools~=1.13.0", "tf2onnx~=1.16.1", "plotly~=5.4.0"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/onnx_utils.ipynb", "source": "src/onnx_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "gen_class_data": {"latest": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.10.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.10.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "gen_class_data", "platformVersion": "3.5.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.6.2", "name": "gen_class_data", "platformVersion": "3.0.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-preparation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "gen_class_data", "platformVersion": "3.2.0", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation"], "description": "Create a binary classification sample dataset and save.", "doc": "", "example": "gen_class_data.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "Daniel"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "gen_class_data", "platformVersion": "3.5.3", "spec": {"filename": "gen_class_data.py", "handler": "gen_class_data", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.3.0", "assets": {"example": "src/gen_class_data.ipynb", "source": "src/gen_class_data.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "azureml_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.4": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0", "plotly~=5.4"]}, "url": "", "version": "0.9.4", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-11-13:00-15", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "commands": null, "image": "", "kind": "job", "requirements": ["azureml-core==1.33.0", "azureml-train-automl-client==1.33.0"]}, "url": "", "version": "0.9.0", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_utils", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["python -m pip install pip==22.1.2", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "1.2.0", "test_valid": false, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.4.0", "test_valid": true, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.5": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2021-04-20:15-18", "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "azureml_utils", "platformVersion": "", "spec": {"filename": "azureml_utils.py", "handler": "train", "extra_spec": {"build": {"commands": ["python -m pip install pip==21.2.4", "apt-get update && apt-get install -y --no-install-recommends git"], "with_mlrun": true, "auto_build": true}, "allow_empty_resources": true}, "image": "python:3.7.9-slim", "kind": "job", "requirements": ["azureml-core==1.40.0", "azureml-train-automl-client==1.40.0", "plotly~=5.4"]}, "url": "", "version": "0.9.5", "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-training"], "description": "Azure AutoML integration in MLRun, including utils functions for training models on Azure AutoML platfrom.", "doc": "", "example": "azureml_utils.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "azureml_utils", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "commands": ["apt-get update && apt-get install -y --no-install-recommends git", "apt install -y liblttng-ust0"], "with_mlrun": true}}, "filename": "azureml_utils.py", "handler": "train", "image": "python:3.9-bullseye", "kind": "job", "requirements": ["azureml-core==1.54.0.post1", "azureml-train-automl-client==1.54.0.post1", "plotly~=5.4"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/azureml_utils.ipynb", "source": "src/azureml_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "describe_spark": {"latest": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "describe-spark", "platformVersion": "3.5.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-05-19:22-41", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "describe-spark", "platformVersion": "", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.1": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe_spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.9.1", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe_spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["data-analysis"], "description": "", "doc": "", "example": "describe_spark.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "describe-spark", "platformVersion": "3.2.0", "spec": {"filename": "describe-spark.py", "handler": "describe_spark", "image": "iguazio/shell:3.0_b5565_20201026062233_wsdf", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/describe_spark.ipynb", "source": "src/describe-spark.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "send_email": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "send-email", "platformVersion": "3.5.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "1.1.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.9.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.9.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-05-19:23-13", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "", "name": "send-email", "platformVersion": "", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.0.1", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "send-email", "platformVersion": "3.5.3", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.2.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.8.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Send Email messages through SMTP server", "doc": "", "example": "send_email.ipynb", "generationDate": "2021-11-18:12-28", "icon": "", "labels": {"author": "saarc"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "0.8.0", "name": "send-email", "platformVersion": "3.2.0", "spec": {"filename": "send_email.py", "handler": "send_email", "image": "mlrun/ml-models", "kind": "job", "requirements": []}, "url": "", "version": "0.8.0", "assets": {"example": "src/send_email.ipynb", "source": "src/send_email.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "arc_to_parquet": {"latest": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/arc_to_parquet.ipynb", "source": "src/arc_to_parquet.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.1": {"apiVersion": "v1", "categories": ["etl"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.4.1", "assets": {"example": "src/arc_to_parquet.ipynb", "source": "src/arc_to_parquet.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "retrieve remote archive, open and save as parquet", "doc": "", "example": "arc_to_parquet.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "avi"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "arc-to-parquet", "platformVersion": "3.5.4", "spec": {"filename": "arc_to_parquet.py", "handler": "arc_to_parquet", "image": "mlrun/mlrun", "kind": "job", "requirements": []}, "url": "", "version": "1.5.0", "assets": {"example": "src/arc_to_parquet.ipynb", "source": "src/arc_to_parquet.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "azureml_serving": {"latest": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_serving.ipynb", "source": "src/azureml_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "model-serving"], "description": "AzureML serving function", "doc": "", "example": "azureml_serving.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "azureml_serving", "platformVersion": "3.5.0", "spec": {"customFields": {"default_class": "mlrun.frameworks.sklearn.PickleModelServer"}, "filename": "azureml_serving.py", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["azureml-automl-runtime~=1.38.1"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/azureml_serving.ipynb", "source": "src/azureml_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "batch_inference": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference ( also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.2.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.7.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.1", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.7.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.8.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.4.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.3.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.1": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": true, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": ["scikit-learn", "plotly"]}, "url": "", "version": "1.1.1", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["utils"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference.ipynb", "generationDate": "2022-08-28:17-25", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.2.0", "name": "batch_inference", "platformVersion": "3.5.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference.py", "handler": "infer", "image": "mlrun/ml-models", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0", "assets": {"example": "src/batch_inference.ipynb", "source": "src/batch_inference.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "hugging_face_serving": {"latest": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false, "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["genai", "model-serving"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.1.0", "test_valid": false, "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["model-serving", "machine-learning"], "description": "Generic Hugging Face model server.", "doc": "", "example": "hugging_face_serving.ipynb", "generationDate": "2022-09-05:17-00", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.1.0", "name": "hugging_face_serving", "platformVersion": "", "spec": {"customFields": {"default_class": "HuggingFaceModelServer"}, "filename": "hugging_face_serving.py", "handler": "handler", "image": "mlrun/ml-models", "kind": "serving", "requirements": ["transformers==4.21.3", "tensorflow==2.9.2"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/hugging_face_serving.ipynb", "source": "src/hugging_face_serving.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "question_answering": {"latest": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.2.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.1": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.3.1", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.4.0": {"apiVersion": "v1", "categories": ["genai", "huggingface", "machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.4.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.1.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": "transformers torch tqdm"}, "url": "", "version": "0.3.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.5.0": {"apiVersion": "v1", "categories": ["genai"], "description": "GenAI approach of question answering on a given data", "doc": "", "example": "question_answering.ipynb", "generationDate": "2023-08-07:11-30", "hidden": false, "icon": "", "labels": {"author": "yonish"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "question_answering", "platformVersion": "3.5.0", "spec": {"filename": "question_answering.py", "handler": "answer_questions", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "torch", "tqdm"]}, "url": "", "version": "0.5.0", "assets": {"example": "src/question_answering.ipynb", "source": "src/question_answering.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "transcribe": {"latest": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "genai", "huggingface", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": false, "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["audio", "genai"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "tqdm", "torchaudio", "torch", "accelerate"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Transcribe audio files into text files", "doc": "", "example": "transcribe.ipynb", "generationDate": "2023-07-13:11-20", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "transcribe", "platformVersion": "3.5.3", "spec": {"filename": "transcribe.py", "handler": "transcribe", "image": "mlrun/mlrun", "kind": "job", "requirements": ["openai-whisper", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true, "assets": {"example": "src/transcribe.ipynb", "source": "src/transcribe.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "pii_recognizer": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.2.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.2.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.0.1", "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.4.0": {"apiVersion": "v1", "categories": ["data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.4.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.1.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "NLP"], "description": "This function is used to recognize PII in a directory of text files", "doc": "", "example": "pii_recognizer.ipynb", "generationDate": "2023-08-15:10-24", "hidden": false, "icon": "", "labels": {"author": "pgw"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.4.0", "name": "pii-recognizer", "platformVersion": "3.5.3", "spec": {"filename": "pii_recognizer.py", "handler": "recognize_pii", "image": "mlrun/mlrun", "kind": "job", "requirements": ["nltk", "pandas", "presidio-anonymizer", "presidio-analyzer", "torch", "flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653", "st-annotated-text", "https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl"]}, "url": "", "version": "0.3.0", "test_valid": false, "assets": {"example": "src/pii_recognizer.ipynb", "source": "src/pii_recognizer.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "batch_inference_v2": {"latest": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.9.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc16", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.9.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.0.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.0.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.1.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.1.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.8.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc13", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.8.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.4.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.4.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.5.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.6.0": {"apiVersion": "v1", "categories": ["model-serving"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc51", "name": "batch_inference_v2", "platformVersion": "3.6.0", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.6.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "2.2.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "2.2.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["utils", "data-analysis", "monitoring"], "description": "Batch inference (also knows as prediction) for the common ML frameworks (SciKit-Learn, XGBoost and LightGBM) while performing data drift analysis.", "doc": "", "example": "batch_inference_v2.ipynb", "generationDate": "2023-08-07:12-25", "hidden": false, "icon": "", "labels": {"author": "eyald"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.0-rc9", "name": "batch_inference_v2", "platformVersion": "3.5.3", "spec": {"extra_spec": {"allow_empty_resources": true, "build": {"auto_build": false, "with_mlrun": false}}, "filename": "batch_inference_v2.py", "handler": "infer", "image": "mlrun/mlrun", "kind": "job", "requirements": null}, "url": "", "version": "1.5.0", "assets": {"example": "src/batch_inference_v2.ipynb", "source": "src/batch_inference_v2.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "translate": {"latest": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.2.0": {"apiVersion": "v1", "categories": ["genai", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.2.0", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.1": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.1", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "huggingface", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.1.0", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "0.0.2": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "deep-learning", "NLP"], "description": "Translate text files from one language to another", "doc": "", "example": "translate.ipynb", "generationDate": "2023-12-05:17-20", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "translate", "platformVersion": "3.5.3", "spec": {"filename": "translate.py", "handler": "translate", "image": "mlrun/mlrun", "kind": "job", "requirements": ["transformers", "sentencepiece", "torch", "tqdm"]}, "url": "", "version": "0.0.2", "test_valid": true, "assets": {"example": "src/translate.ipynb", "source": "src/translate.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "structured_data_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.6.0": {"apiVersion": "v1", "categories": ["data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.6.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.4.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "structured_data_generator", "platformVersion": "3.5.0", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.5.0": {"apiVersion": "v1", "categories": ["machine-learning", "data-preparation", "data-generation", "genai"], "description": "GenAI approach of generating structured data according to a given schema", "doc": "", "example": "structured_data_generator.ipynb", "generationDate": "2023-12-14:10-50", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.6.1", "name": "structured_data_generator", "platformVersion": "3.5.5", "spec": {"filename": "structured_data_generator.py", "handler": "generate_data", "image": "mlrun/mlrun", "kind": "job", "requirements": ["langchain", "tqdm"]}, "url": "", "version": "1.5.0", "assets": {"example": "src/structured_data_generator.ipynb", "source": "src/structured_data_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "text_to_audio_generator": {"latest": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.1.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning", "pytorch"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.2.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["bark", "torchaudio"]}, "url": "", "version": "1.0.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["data-generation", "audio"], "description": "Generate audio file from text using different speakers", "doc": "", "example": "text_to_audio_generator.ipynb", "generationDate": "2023-12-03:15-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.1", "name": "text_to_audio_generator", "platformVersion": "3.5.3", "spec": {"filename": "text_to_audio_generator.py", "handler": "generate_multi_speakers_audio", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torchaudio", "pydub"]}, "url": "", "version": "1.3.0", "test_valid": true, "assets": {"example": "src/text_to_audio_generator.ipynb", "source": "src/text_to_audio_generator.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "silero_vad": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.4.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.4.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "pytorch", "audio"], "description": "Silero VAD (Voice Activity Detection) functions.", "doc": "", "example": "silero_vad.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "silero_vad", "platformVersion": "3.5.3", "spec": {"filename": "silero_vad.py", "handler": "detect_voice", "image": "mlrun/mlrun", "kind": "job", "requirements": ["torch", "torchaudio", "tqdm", "onnxruntime"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/silero_vad.ipynb", "source": "src/silero_vad.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "pyannote_audio": {"latest": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.2.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.2.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["deep-learning", "huggingface", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.5.2", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.3.0": {"apiVersion": "v1", "categories": ["deep-learning", "audio"], "description": "pyannote's speech diarization of audio files", "doc": "", "example": "pyannote_audio.ipynb", "generationDate": "2023-12-03:14-30", "hidden": false, "icon": "", "labels": {"author": "guyl"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0", "name": "pyannote-audio", "platformVersion": "3.5.3", "spec": {"filename": "pyannote_audio.py", "handler": "diarize", "image": "mlrun/mlrun-gpu", "kind": "job", "requirements": ["pyannote.audio", "pyannote.core", "torchaudio", "tqdm"]}, "url": "", "version": "1.3.0", "assets": {"example": "src/pyannote_audio.ipynb", "source": "src/pyannote_audio.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "mlflow_utils": {"latest": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/mlflow_utils.ipynb", "source": "src/mlflow_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["model-serving", "utils"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.8.0", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/mlflow_utils.ipynb", "source": "src/mlflow_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["genai", "model-serving", "machine-learning"], "description": "Mlflow model server, and additional utils.", "doc": "", "example": "mlflow_utils.ipynb", "generationDate": "2024-05-23:12-00", "hidden": false, "icon": "", "labels": {"author": "zeevr"}, "maintainers": [], "marketplaceType": "", "mlrunVersion": "1.7.0-rc17", "name": "mlflow_utils", "platformVersion": "", "spec": {"customFields": {"default_class": "MLFlowModelServer"}, "filename": "mlflow_utils.py", "handler": "handler", "image": "mlrun/mlrun", "kind": "serving", "requirements": ["mlflow==2.12.2", "lightgbm", "xgboost"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/mlflow_utils.ipynb", "source": "src/mlflow_utils.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}, "noise_reduction": {"latest": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/noise_reduction.ipynb", "source": "src/noise_reduction.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.1.0": {"apiVersion": "v1", "categories": ["data-preparation", "audio"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.7.0", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.1.0", "assets": {"example": "src/noise_reduction.ipynb", "source": "src/noise_reduction.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}, "1.0.0": {"apiVersion": "v1", "categories": ["data-preparation", "machine-learning"], "description": "Reduce noise from audio files", "doc": "", "example": "noise_reduction.ipynb", "generationDate": "2024-03-04:17-30", "hidden": false, "icon": "", "labels": {"author": "yonatans"}, "maintainers": [], "mlrunVersion": "1.5.2", "name": "noise-reduction", "platformVersion": "3.5.3", "spec": {"filename": "noise_reduction.py", "handler": "reduce_noise", "image": "mlrun/mlrun", "kind": "job", "requirements": ["librosa", "noisereduce", "deepfilternet", "torchaudio>=2.1.2"]}, "url": "", "version": "1.0.0", "assets": {"example": "src/noise_reduction.ipynb", "source": "src/noise_reduction.py", "function": "src/function.yaml", "docs": "static/documentation.html"}}}}
\ No newline at end of file
diff --git a/functions/master/describe/1.3.0/static/describe.html b/functions/master/describe/1.3.0/static/describe.html
index 698c8923..16609921 100644
--- a/functions/master/describe/1.3.0/static/describe.html
+++ b/functions/master/describe/1.3.0/static/describe.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe/1.3.0/static/documentation.html b/functions/master/describe/1.3.0/static/documentation.html
index 21c81d34..5c15dcbd 100644
--- a/functions/master/describe/1.3.0/static/documentation.html
+++ b/functions/master/describe/1.3.0/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe/1.3.0/static/example.html b/functions/master/describe/1.3.0/static/example.html
index d2932314..a35cd80e 100644
--- a/functions/master/describe/1.3.0/static/example.html
+++ b/functions/master/describe/1.3.0/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe/latest/static/describe.html b/functions/master/describe/latest/static/describe.html
index 698c8923..16609921 100644
--- a/functions/master/describe/latest/static/describe.html
+++ b/functions/master/describe/latest/static/describe.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe/latest/static/documentation.html b/functions/master/describe/latest/static/documentation.html
index 21c81d34..5c15dcbd 100644
--- a/functions/master/describe/latest/static/documentation.html
+++ b/functions/master/describe/latest/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe/latest/static/example.html b/functions/master/describe/latest/static/example.html
index d2932314..a35cd80e 100644
--- a/functions/master/describe/latest/static/example.html
+++ b/functions/master/describe/latest/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/1.1.0/static/describe_dask.html b/functions/master/describe_dask/1.1.0/static/describe_dask.html
index 7125e14e..b006612c 100644
--- a/functions/master/describe_dask/1.1.0/static/describe_dask.html
+++ b/functions/master/describe_dask/1.1.0/static/describe_dask.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/1.1.0/static/documentation.html b/functions/master/describe_dask/1.1.0/static/documentation.html
index 972f2d66..1d8ae7b1 100644
--- a/functions/master/describe_dask/1.1.0/static/documentation.html
+++ b/functions/master/describe_dask/1.1.0/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/1.1.0/static/example.html b/functions/master/describe_dask/1.1.0/static/example.html
index a6df31db..8a61fe6a 100644
--- a/functions/master/describe_dask/1.1.0/static/example.html
+++ b/functions/master/describe_dask/1.1.0/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/latest/static/describe_dask.html b/functions/master/describe_dask/latest/static/describe_dask.html
index 7125e14e..b006612c 100644
--- a/functions/master/describe_dask/latest/static/describe_dask.html
+++ b/functions/master/describe_dask/latest/static/describe_dask.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/latest/static/documentation.html b/functions/master/describe_dask/latest/static/documentation.html
index 972f2d66..1d8ae7b1 100644
--- a/functions/master/describe_dask/latest/static/documentation.html
+++ b/functions/master/describe_dask/latest/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_dask/latest/static/example.html b/functions/master/describe_dask/latest/static/example.html
index a6df31db..8a61fe6a 100644
--- a/functions/master/describe_dask/latest/static/example.html
+++ b/functions/master/describe_dask/latest/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_spark/1.1.0/static/documentation.html b/functions/master/describe_spark/1.1.0/static/documentation.html
index 606e76b2..734090e4 100644
--- a/functions/master/describe_spark/1.1.0/static/documentation.html
+++ b/functions/master/describe_spark/1.1.0/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_spark/1.1.0/static/example.html b/functions/master/describe_spark/1.1.0/static/example.html
index 7d07ac2f..7032fa9a 100644
--- a/functions/master/describe_spark/1.1.0/static/example.html
+++ b/functions/master/describe_spark/1.1.0/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_spark/latest/static/documentation.html b/functions/master/describe_spark/latest/static/documentation.html
index 606e76b2..734090e4 100644
--- a/functions/master/describe_spark/latest/static/documentation.html
+++ b/functions/master/describe_spark/latest/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/describe_spark/latest/static/example.html b/functions/master/describe_spark/latest/static/example.html
index 7d07ac2f..7032fa9a 100644
--- a/functions/master/describe_spark/latest/static/example.html
+++ b/functions/master/describe_spark/latest/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/1.6.0/static/documentation.html b/functions/master/feature_selection/1.6.0/static/documentation.html
index 65ce50d5..a136a710 100644
--- a/functions/master/feature_selection/1.6.0/static/documentation.html
+++ b/functions/master/feature_selection/1.6.0/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/1.6.0/static/example.html b/functions/master/feature_selection/1.6.0/static/example.html
index 3bbfa58c..e4c5804f 100644
--- a/functions/master/feature_selection/1.6.0/static/example.html
+++ b/functions/master/feature_selection/1.6.0/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/1.6.0/static/feature_selection.html b/functions/master/feature_selection/1.6.0/static/feature_selection.html
index d2696107..48afd201 100644
--- a/functions/master/feature_selection/1.6.0/static/feature_selection.html
+++ b/functions/master/feature_selection/1.6.0/static/feature_selection.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/latest/static/documentation.html b/functions/master/feature_selection/latest/static/documentation.html
index 65ce50d5..a136a710 100644
--- a/functions/master/feature_selection/latest/static/documentation.html
+++ b/functions/master/feature_selection/latest/static/documentation.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/latest/static/example.html b/functions/master/feature_selection/latest/static/example.html
index 3bbfa58c..e4c5804f 100644
--- a/functions/master/feature_selection/latest/static/example.html
+++ b/functions/master/feature_selection/latest/static/example.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/feature_selection/latest/static/feature_selection.html b/functions/master/feature_selection/latest/static/feature_selection.html
index d2696107..48afd201 100644
--- a/functions/master/feature_selection/latest/static/feature_selection.html
+++ b/functions/master/feature_selection/latest/static/feature_selection.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/gen_class_data/1.3.0/src/function.yaml b/functions/master/gen_class_data/1.3.0/src/function.yaml
new file mode 100644
index 00000000..1769bec0
--- /dev/null
+++ b/functions/master/gen_class_data/1.3.0/src/function.yaml
@@ -0,0 +1,72 @@
+metadata:
+  categories:
+  - data-generation
+  tag: ''
+  name: gen-class-data
+spec:
+  description: Create a binary classification sample dataset and save.
+  default_handler: gen_class_data
+  entry_points:
+    gen_class_data:
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: function context
+      - name: n_samples
+        type: int
+        doc: number of rows/samples
+      - name: m_features
+        type: int
+        doc: number of cols/features
+      - name: k_classes
+        type: int
+        doc: number of classes
+      - name: header
+        type: Optional[List[str]]
+        doc: header for features array
+      - name: label_column
+        type: Optional[str]
+        doc: column name of ground-truth series
+        default: labels
+      - name: weight
+        type: float
+        doc: fraction of sample negative value (ground-truth=0)
+        default: 0.5
+      - name: random_state
+        type: int
+        doc: rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)
+        default: 1
+      - name: key
+        type: str
+        doc: key of data in artifact store
+        default: classifier-data
+      - name: file_ext
+        type: str
+        doc: (pqt) extension for parquet file
+        default: parquet
+      - name: sk_params
+        doc: additional parameters for `sklearn.datasets.make_classification`
+        default: {}
+      lineno: 22
+      doc: 'Create a binary classification sample dataset and save.
+
+        If no filename is given it will default to:
+
+        "simdata-{n_samples}X{m_features}.parquet".
+
+
+        Additional scikit-learn parameters can be set using **sk_params, please see
+        https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html
+        for more details.'
+      has_varargs: false
+      name: gen_class_data
+  command: ''
+  disable_auto_mount: false
+  image: mlrun/mlrun
+  build:
+    origin_filename: ''
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo=
+    code_origin: ''
+kind: job
+verbose: false
diff --git a/functions/master/gen_class_data/1.3.0/src/gen_class_data.ipynb b/functions/master/gen_class_data/1.3.0/src/gen_class_data.ipynb
new file mode 100644
index 00000000..5335e646
--- /dev/null
+++ b/functions/master/gen_class_data/1.3.0/src/gen_class_data.ipynb
@@ -0,0 +1,685 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Generate classification data\n",
+    "\n",
+    "Use this function to generate sample data sets, wraps scikit-learn's **[make_classification](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html#sklearn-datasets-make-classification)**.  See the link for a description of all parameters."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# nuclio: ignore\n",
+    "import nuclio"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "import pandas as pd\n",
+    "import pyarrow as pa\n",
+    "import pyarrow.parquet as pq\n",
+    "from typing import Optional, List, Any\n",
+    "from sklearn.datasets import make_classification\n",
+    "\n",
+    "from mlrun.execution import MLClientCtx\n",
+    "\n",
+    "def gen_class_data(\n",
+    "    context: MLClientCtx,\n",
+    "    n_samples: int,\n",
+    "    m_features: int,\n",
+    "    k_classes: int,\n",
+    "    header: Optional[List[str]],\n",
+    "    label_column: Optional[str] = \"labels\",\n",
+    "    weight: float = 0.5,\n",
+    "    random_state: int = 1,\n",
+    "    key: str = \"classifier-data\", \n",
+    "    file_ext: str = \"parquet\",\n",
+    "    sk_params = {}\n",
+    "):\n",
+    "    \"\"\"Create a binary classification sample dataset and save.\n",
+    "    If no filename is given it will default to:\n",
+    "    \"simdata-{n_samples}X{m_features}.parquet\".\n",
+    "    \n",
+    "    Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details.\n",
+    "    \n",
+    "    :param context:       function context\n",
+    "    :param n_samples:     number of rows/samples\n",
+    "    :param m_features:    number of cols/features\n",
+    "    :param k_classes:     number of classes\n",
+    "    :param header:        header for features array\n",
+    "    :param label_column:  column name of ground-truth series\n",
+    "    :param weight:        fraction of sample negative value (ground-truth=0)\n",
+    "    :param random_state:  rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)\n",
+    "    :param key:           key of data in artifact store\n",
+    "    :param file_ext:      (pqt) extension for parquet file\n",
+    "    :param sk_params:     additional parameters for `sklearn.datasets.make_classification`\n",
+    "    \"\"\"\n",
+    "    features, labels = make_classification(\n",
+    "        n_samples=n_samples,\n",
+    "        n_features=m_features,\n",
+    "        weights=weight,\n",
+    "        n_classes=k_classes,\n",
+    "        random_state=random_state, \n",
+    "        **sk_params)\n",
+    "\n",
+    "    # make dataframes, add column names, concatenate (X, y)\n",
+    "    X = pd.DataFrame(features)\n",
+    "    if not header:\n",
+    "        X.columns = [\"feat_\" + str(x) for x in range(m_features)]\n",
+    "    else:\n",
+    "        X.columns = header\n",
+    "\n",
+    "    y = pd.DataFrame(labels, columns=[label_column])\n",
+    "    data = pd.concat([X, y], axis=1)\n",
+    "    \n",
+    "    context.log_dataset(key, df=data, format=file_ext, index=False)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# nuclio: end-code"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### save"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "[mlrun] 2020-06-14 10:37:07,647 function spec saved to path: function.yaml\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       ""
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from mlrun import code_to_function\n",
+    "from mlrun.platforms.other import auto_mount\n",
+    "\n",
+    "gpus = False\n",
+    "\n",
+    "fn_params = {\n",
+    "    \"name\"        : \"gen_class_data\",\n",
+    "    \"handler\"     : \"gen_class_data\",\n",
+    "    \"kind\"        : \"job\",\n",
+    "    \"image\"       : \"mlrun/ml-models\" if not gpus else \"mlrun/ml-models-gpu\",\n",
+    "    \"description\" : \"simulate classification data using scikit-learn\",\n",
+    "    \"categories\"  : [\"simulators\", \"ml\"],\n",
+    "    \"labels\"      : {\"author\": \"yjb\", 'framework': 'sklearn'},\n",
+    "}\n",
+    "\n",
+    "fn = code_to_function(**fn_params)\n",
+    "\n",
+    "fn.export(\"function.yaml\")\n",
+    "fn.apply(auto_mount())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### test function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from mlrun import NewTask, mlconf\n",
+    "\n",
+    "task_params = {\n",
+    "    \"name\":        \"tasks generate classification data\", \n",
+    "    \"params\" : {\n",
+    "        \"n_samples\"   : 10_000,\n",
+    "        \"m_features\"  : 5,\n",
+    "        \"k_classes\"   : 2,\n",
+    "        \"weight\"      : [0.5, 0.5],\n",
+    "        \"sk_params\"   : {\"n_informative\": 2},\n",
+    "        \"file_ext\"    : \"csv\"}}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### local"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "[mlrun] 2020-06-14 10:33:01,963 starting run tasks generate classification data uid=1d7c5af7e4b04bd98755c87842455105  -> http://mlrun-api:8080\n",
+      "[mlrun] 2020-06-14 10:33:02,156 log artifact classifier-data at /User/artifacts/classifier-data.csv, size: 998700, db: Y\n",
+      "\n"
+     ]
+    },
+    {
+     "data": {
+      "text/html": [
+       "\n",
+       "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Jun 14 10:33:01completedtasks generate classification data
v3io_user=admin
kind=handler
owner=admin
host=jupyter-7b44c8d958-kklf7
n_samples=10000
m_features=5
k_classes=2
weight=[0.5, 0.5]
sk_params={'n_informative': 2}
file_ext=csv
classifier-data
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "to track results use .show() or .logs() or in CLI: \n", + "!mlrun get run 1d7c5af7e4b04bd98755c87842455105 --project default , !mlrun logs 1d7c5af7e4b04bd98755c87842455105 --project default\n", + "[mlrun] 2020-06-14 10:33:02,198 run executed, status=completed\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from mlrun import run_local\n", + "run_local(NewTask(**task_params), handler=gen_class_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### remote" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[mlrun] 2020-06-14 10:33:02,619 starting run tasks generate classification data uid=8f2102b308f446f28242c03ac1a835a7 -> http://mlrun-api:8080\n", + "[mlrun] 2020-06-14 10:33:02,723 Job is running in the background, pod: tasks-generate-classification-data-wjdsf\n", + "[mlrun] 2020-06-14 10:33:08,285 starting local run: main.py # gen_class_data\n", + "[mlrun] 2020-06-14 10:33:08,806 log artifact classifier-data at /User/artifacts/classifier-data.csv, size: 998700, db: Y\n", + "\n", + "[mlrun] 2020-06-14 10:33:08,823 run executed, status=completed\n", + "final state: succeeded\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Jun 14 10:33:08completedtasks generate classification data
v3io_user=admin
kind=job
owner=admin
host=tasks-generate-classification-data-wjdsf
n_samples=10000
m_features=5
k_classes=2
weight=[0.5, 0.5]
sk_params={'n_informative': 2}
file_ext=csv
classifier-data
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "to track results use .show() or .logs() or in CLI: \n", + "!mlrun get run 8f2102b308f446f28242c03ac1a835a7 --project default , !mlrun logs 8f2102b308f446f28242c03ac1a835a7 --project default\n", + "[mlrun] 2020-06-14 10:33:11,884 run executed, status=completed\n" + ] + } + ], + "source": [ + "run = fn.run(NewTask(**task_params), artifact_path=mlconf.artifact_path)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/functions/master/gen_class_data/1.3.0/src/gen_class_data.py b/functions/master/gen_class_data/1.3.0/src/gen_class_data.py new file mode 100644 index 00000000..2e5ab107 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/src/gen_class_data.py @@ -0,0 +1,71 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import pandas as pd +from typing import Optional, List +from sklearn.datasets import make_classification + +from mlrun.execution import MLClientCtx + + +def gen_class_data( + context: MLClientCtx, + n_samples: int, + m_features: int, + k_classes: int, + header: Optional[List[str]], + label_column: Optional[str] = "labels", + weight: float = 0.5, + random_state: int = 1, + key: str = "classifier-data", + file_ext: str = "parquet", + sk_params={} +): + """Create a binary classification sample dataset and save. + If no filename is given it will default to: + "simdata-{n_samples}X{m_features}.parquet". + + Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details. + + :param context: function context + :param n_samples: number of rows/samples + :param m_features: number of cols/features + :param k_classes: number of classes + :param header: header for features array + :param label_column: column name of ground-truth series + :param weight: fraction of sample negative value (ground-truth=0) + :param random_state: rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state) + :param key: key of data in artifact store + :param file_ext: (pqt) extension for parquet file + :param sk_params: additional parameters for `sklearn.datasets.make_classification` + """ + features, labels = make_classification( + n_samples=n_samples, + n_features=m_features, + weights=weight, + n_classes=k_classes, + random_state=random_state, + **sk_params) + + # make dataframes, add column names, concatenate (X, y) + X = pd.DataFrame(features) + if not header: + X.columns = ["feat_" + str(x) for x in range(m_features)] + else: + X.columns = header + + y = pd.DataFrame(labels, columns=[label_column]) + data = pd.concat([X, y], axis=1) + + context.log_dataset(key, df=data, format=file_ext, index=False) diff --git a/functions/master/gen_class_data/1.3.0/src/item.yaml b/functions/master/gen_class_data/1.3.0/src/item.yaml new file mode 100644 index 00000000..a6dd94b6 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/src/item.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +categories: +- data-generation +description: Create a binary classification sample dataset and save. +doc: '' +example: gen_class_data.ipynb +generationDate: 2022-08-28:17-25 +hidden: false +icon: '' +labels: + author: Daniel +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: gen_class_data +platformVersion: 3.5.3 +spec: + filename: gen_class_data.py + handler: gen_class_data + image: mlrun/mlrun + kind: job + requirements: [] +url: '' +version: 1.3.0 diff --git a/functions/master/gen_class_data/1.3.0/src/requirements.txt b/functions/master/gen_class_data/1.3.0/src/requirements.txt new file mode 100644 index 00000000..d7dbe376 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/src/requirements.txt @@ -0,0 +1,2 @@ +pandas +scikit-learn==1.0.2 \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/src/test_gen_class_data.py b/functions/master/gen_class_data/1.3.0/src/test_gen_class_data.py new file mode 100644 index 00000000..e06eeb16 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/src/test_gen_class_data.py @@ -0,0 +1,39 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from mlrun import code_to_function +import os + + +def test_gen_class_data(): + fn = code_to_function( + name='test_gen_class_data', + filename="gen_class_data.py", + handler="gen_class_data", + kind="job", + ) + + run = fn.run( + params={ + "n_samples": 10_000, + "m_features": 5, + "k_classes": 2, + "header": None, + "weight": [0.5, 0.5], + "sk_params": {"n_informative": 2}, + "file_ext": "csv"}, + local=True, + artifact_path="./artifacts", + ) + assert os.path.isfile(run.status.artifacts[0]['spec']['target_path']), 'dataset is not available' diff --git a/functions/master/gen_class_data/1.3.0/static/documentation.html b/functions/master/gen_class_data/1.3.0/static/documentation.html new file mode 100644 index 00000000..7125e01f --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/documentation.html @@ -0,0 +1,261 @@ + + + + + + + +gen_class_data package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

gen_class_data package

+ +
+ +
+
+ +
+
+

gen_class_data package#

+
+

Submodules#

+
+
+

gen_class_data.gen_class_data module#

+
+
+gen_class_data.gen_class_data.gen_class_data(context: MLClientCtx, n_samples: int, m_features: int, k_classes: int, header: List[str] | None, label_column: str | None = 'labels', weight: float = 0.5, random_state: int = 1, key: str = 'classifier-data', file_ext: str = 'parquet', sk_params={})[source]#
+

Create a binary classification sample dataset and save. +If no filename is given it will default to: +“simdata-{n_samples}X{m_features}.parquet”.

+

Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details.

+
+
Parameters:
+
    +
  • context – function context

  • +
  • n_samples – number of rows/samples

  • +
  • m_features – number of cols/features

  • +
  • k_classes – number of classes

  • +
  • header – header for features array

  • +
  • label_column – column name of ground-truth series

  • +
  • weight – fraction of sample negative value (ground-truth=0)

  • +
  • random_state – rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)

  • +
  • key – key of data in artifact store

  • +
  • file_ext – (pqt) extension for parquet file

  • +
  • sk_params – additional parameters for sklearn.datasets.make_classification

  • +
+
+
+
+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/static/example.html b/functions/master/gen_class_data/1.3.0/static/example.html new file mode 100644 index 00000000..853b6a66 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/example.html @@ -0,0 +1,791 @@ + + + + + + + +Generate classification data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+

Generate classification data

+ +
+
+
+

Contents

+
+ +
+
+
+ +
+
+

Generate classification data#

+

Use this function to generate sample data sets, wraps scikit-learn’s make_classification. See the link for a description of all parameters.

+
+
+
# nuclio: ignore
+import nuclio
+
+
+
+
+
+
+
import os
+import pandas as pd
+import pyarrow as pa
+import pyarrow.parquet as pq
+from typing import Optional, List, Any
+from sklearn.datasets import make_classification
+
+from mlrun.execution import MLClientCtx
+
+def gen_class_data(
+    context: MLClientCtx,
+    n_samples: int,
+    m_features: int,
+    k_classes: int,
+    header: Optional[List[str]],
+    label_column: Optional[str] = "labels",
+    weight: float = 0.5,
+    random_state: int = 1,
+    key: str = "classifier-data", 
+    file_ext: str = "parquet",
+    sk_params = {}
+):
+    """Create a binary classification sample dataset and save.
+    If no filename is given it will default to:
+    "simdata-{n_samples}X{m_features}.parquet".
+    
+    Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details.
+    
+    :param context:       function context
+    :param n_samples:     number of rows/samples
+    :param m_features:    number of cols/features
+    :param k_classes:     number of classes
+    :param header:        header for features array
+    :param label_column:  column name of ground-truth series
+    :param weight:        fraction of sample negative value (ground-truth=0)
+    :param random_state:  rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)
+    :param key:           key of data in artifact store
+    :param file_ext:      (pqt) extension for parquet file
+    :param sk_params:     additional parameters for `sklearn.datasets.make_classification`
+    """
+    features, labels = make_classification(
+        n_samples=n_samples,
+        n_features=m_features,
+        weights=weight,
+        n_classes=k_classes,
+        random_state=random_state, 
+        **sk_params)
+
+    # make dataframes, add column names, concatenate (X, y)
+    X = pd.DataFrame(features)
+    if not header:
+        X.columns = ["feat_" + str(x) for x in range(m_features)]
+    else:
+        X.columns = header
+
+    y = pd.DataFrame(labels, columns=[label_column])
+    data = pd.concat([X, y], axis=1)
+    
+    context.log_dataset(key, df=data, format=file_ext, index=False)
+
+
+
+
+
+
+
# nuclio: end-code
+
+
+
+
+
+

save#

+
+
+
from mlrun import code_to_function
+from mlrun.platforms.other import auto_mount
+
+gpus = False
+
+fn_params = {
+    "name"        : "gen_class_data",
+    "handler"     : "gen_class_data",
+    "kind"        : "job",
+    "image"       : "mlrun/ml-models" if not gpus else "mlrun/ml-models-gpu",
+    "description" : "simulate classification data using scikit-learn",
+    "categories"  : ["simulators", "ml"],
+    "labels"      : {"author": "yjb", 'framework': 'sklearn'},
+}
+
+fn = code_to_function(**fn_params)
+
+fn.export("function.yaml")
+fn.apply(auto_mount())
+
+
+
+
+
[mlrun] 2020-06-14 10:37:07,647 function spec saved to path: function.yaml
+
+
+
<mlrun.runtimes.kubejob.KubejobRuntime at 0x7faf4a975eb8>
+
+
+
+
+
+
+

test function#

+
+
+
from mlrun import NewTask, mlconf
+
+task_params = {
+    "name":        "tasks generate classification data", 
+    "params" : {
+        "n_samples"   : 10_000,
+        "m_features"  : 5,
+        "k_classes"   : 2,
+        "weight"      : [0.5, 0.5],
+        "sk_params"   : {"n_informative": 2},
+        "file_ext"    : "csv"}}
+
+
+
+
+
+
+

local#

+
+
+
from mlrun import run_local
+run_local(NewTask(**task_params), handler=gen_class_data)
+
+
+
+
+
[mlrun] 2020-06-14 10:33:01,963 starting run tasks generate classification data uid=1d7c5af7e4b04bd98755c87842455105  -> http://mlrun-api:8080
+[mlrun] 2020-06-14 10:33:02,156 log artifact classifier-data at /User/artifacts/classifier-data.csv, size: 998700, db: Y
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Jun 14 10:33:01completedtasks generate classification data
v3io_user=admin
kind=handler
owner=admin
host=jupyter-7b44c8d958-kklf7
n_samples=10000
m_features=5
k_classes=2
weight=[0.5, 0.5]
sk_params={'n_informative': 2}
file_ext=csv
classifier-data
+
+ +
+
to track results use .show() or .logs() or in CLI: 
+!mlrun get run 1d7c5af7e4b04bd98755c87842455105 --project default , !mlrun logs 1d7c5af7e4b04bd98755c87842455105 --project default
+[mlrun] 2020-06-14 10:33:02,198 run executed, status=completed
+
+
+
<mlrun.model.RunObject at 0x7fafa49fc160>
+
+
+
+
+
+
+

remote#

+
+
+
run = fn.run(NewTask(**task_params), artifact_path=mlconf.artifact_path)
+
+
+
+
+
[mlrun] 2020-06-14 10:33:02,619 starting run tasks generate classification data uid=8f2102b308f446f28242c03ac1a835a7  -> http://mlrun-api:8080
+[mlrun] 2020-06-14 10:33:02,723 Job is running in the background, pod: tasks-generate-classification-data-wjdsf
+[mlrun] 2020-06-14 10:33:08,285 starting local run: main.py # gen_class_data
+[mlrun] 2020-06-14 10:33:08,806 log artifact classifier-data at /User/artifacts/classifier-data.csv, size: 998700, db: Y
+
+[mlrun] 2020-06-14 10:33:08,823 run executed, status=completed
+final state: succeeded
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
default0Jun 14 10:33:08completedtasks generate classification data
v3io_user=admin
kind=job
owner=admin
host=tasks-generate-classification-data-wjdsf
n_samples=10000
m_features=5
k_classes=2
weight=[0.5, 0.5]
sk_params={'n_informative': 2}
file_ext=csv
classifier-data
+
+ +
+
to track results use .show() or .logs() or in CLI: 
+!mlrun get run 8f2102b308f446f28242c03ac1a835a7 --project default , !mlrun logs 8f2102b308f446f28242c03ac1a835a7 --project default
+[mlrun] 2020-06-14 10:33:11,884 run executed, status=completed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/static/function.html b/functions/master/gen_class_data/1.3.0/static/function.html new file mode 100644 index 00000000..f4605fe7 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/function.html @@ -0,0 +1,107 @@ + + + + + + + + + + + Source + + + + +
+        
+metadata:
+  categories:
+  - data-generation
+  tag: ''
+  name: gen-class-data
+spec:
+  description: Create a binary classification sample dataset and save.
+  default_handler: gen_class_data
+  entry_points:
+    gen_class_data:
+      has_kwargs: false
+      parameters:
+      - name: context
+        type: MLClientCtx
+        doc: function context
+      - name: n_samples
+        type: int
+        doc: number of rows/samples
+      - name: m_features
+        type: int
+        doc: number of cols/features
+      - name: k_classes
+        type: int
+        doc: number of classes
+      - name: header
+        type: Optional[List[str]]
+        doc: header for features array
+      - name: label_column
+        type: Optional[str]
+        doc: column name of ground-truth series
+        default: labels
+      - name: weight
+        type: float
+        doc: fraction of sample negative value (ground-truth=0)
+        default: 0.5
+      - name: random_state
+        type: int
+        doc: rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)
+        default: 1
+      - name: key
+        type: str
+        doc: key of data in artifact store
+        default: classifier-data
+      - name: file_ext
+        type: str
+        doc: (pqt) extension for parquet file
+        default: parquet
+      - name: sk_params
+        doc: additional parameters for `sklearn.datasets.make_classification`
+        default: {}
+      lineno: 22
+      doc: 'Create a binary classification sample dataset and save.
+
+        If no filename is given it will default to:
+
+        "simdata-{n_samples}X{m_features}.parquet".
+
+
+        Additional scikit-learn parameters can be set using **sk_params, please see
+        https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html
+        for more details.'
+      has_varargs: false
+      name: gen_class_data
+  command: ''
+  disable_auto_mount: false
+  image: mlrun/mlrun
+  build:
+    origin_filename: ''
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo=
+    code_origin: ''
+kind: job
+verbose: false
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/static/gen_class_data.html b/functions/master/gen_class_data/1.3.0/static/gen_class_data.html new file mode 100644 index 00000000..8641ff05 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/gen_class_data.html @@ -0,0 +1,246 @@ + + + + + + + +gen_class_data.gen_class_data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ +
+
+
+
+
+ +
+

Source code for gen_class_data.gen_class_data

+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import pandas as pd
+from typing import Optional, List
+from sklearn.datasets import make_classification
+
+from mlrun.execution import MLClientCtx
+
+
+
+[docs] +def gen_class_data( + context: MLClientCtx, + n_samples: int, + m_features: int, + k_classes: int, + header: Optional[List[str]], + label_column: Optional[str] = "labels", + weight: float = 0.5, + random_state: int = 1, + key: str = "classifier-data", + file_ext: str = "parquet", + sk_params={} +): + """Create a binary classification sample dataset and save. + If no filename is given it will default to: + "simdata-{n_samples}X{m_features}.parquet". + + Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details. + + :param context: function context + :param n_samples: number of rows/samples + :param m_features: number of cols/features + :param k_classes: number of classes + :param header: header for features array + :param label_column: column name of ground-truth series + :param weight: fraction of sample negative value (ground-truth=0) + :param random_state: rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state) + :param key: key of data in artifact store + :param file_ext: (pqt) extension for parquet file + :param sk_params: additional parameters for `sklearn.datasets.make_classification` + """ + features, labels = make_classification( + n_samples=n_samples, + n_features=m_features, + weights=weight, + n_classes=k_classes, + random_state=random_state, + **sk_params) + + # make dataframes, add column names, concatenate (X, y) + X = pd.DataFrame(features) + if not header: + X.columns = ["feat_" + str(x) for x in range(m_features)] + else: + X.columns = header + + y = pd.DataFrame(labels, columns=[label_column]) + data = pd.concat([X, y], axis=1) + + context.log_dataset(key, df=data, format=file_ext, index=False)
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/static/item.html b/functions/master/gen_class_data/1.3.0/static/item.html new file mode 100644 index 00000000..b1943f72 --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/item.html @@ -0,0 +1,59 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+- data-generation
+description: Create a binary classification sample dataset and save.
+doc: ''
+example: gen_class_data.ipynb
+generationDate: 2022-08-28:17-25
+hidden: false
+icon: ''
+labels:
+  author: Daniel
+maintainers: []
+marketplaceType: ''
+mlrunVersion: 1.7.0
+name: gen_class_data
+platformVersion: 3.5.3
+spec:
+  filename: gen_class_data.py
+  handler: gen_class_data
+  image: mlrun/mlrun
+  kind: job
+  requirements: []
+url: ''
+version: 1.3.0
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/1.3.0/static/source.html b/functions/master/gen_class_data/1.3.0/static/source.html new file mode 100644 index 00000000..8c733cda --- /dev/null +++ b/functions/master/gen_class_data/1.3.0/static/source.html @@ -0,0 +1,106 @@ + + + + + + + + + + + Source + + + + +
+        
+# Copyright 2019 Iguazio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import pandas as pd
+from typing import Optional, List
+from sklearn.datasets import make_classification
+
+from mlrun.execution import MLClientCtx
+
+
+def gen_class_data(
+        context: MLClientCtx,
+        n_samples: int,
+        m_features: int,
+        k_classes: int,
+        header: Optional[List[str]],
+        label_column: Optional[str] = "labels",
+        weight: float = 0.5,
+        random_state: int = 1,
+        key: str = "classifier-data",
+        file_ext: str = "parquet",
+        sk_params={}
+):
+    """Create a binary classification sample dataset and save.
+    If no filename is given it will default to:
+    "simdata-{n_samples}X{m_features}.parquet".
+
+    Additional scikit-learn parameters can be set using **sk_params, please see https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html for more details.
+
+    :param context:       function context
+    :param n_samples:     number of rows/samples
+    :param m_features:    number of cols/features
+    :param k_classes:     number of classes
+    :param header:        header for features array
+    :param label_column:  column name of ground-truth series
+    :param weight:        fraction of sample negative value (ground-truth=0)
+    :param random_state:  rng seed (see https://scikit-learn.org/stable/glossary.html#term-random-state)
+    :param key:           key of data in artifact store
+    :param file_ext:      (pqt) extension for parquet file
+    :param sk_params:     additional parameters for `sklearn.datasets.make_classification`
+    """
+    features, labels = make_classification(
+        n_samples=n_samples,
+        n_features=m_features,
+        weights=weight,
+        n_classes=k_classes,
+        random_state=random_state,
+        **sk_params)
+
+    # make dataframes, add column names, concatenate (X, y)
+    X = pd.DataFrame(features)
+    if not header:
+        X.columns = ["feat_" + str(x) for x in range(m_features)]
+    else:
+        X.columns = header
+
+    y = pd.DataFrame(labels, columns=[label_column])
+    data = pd.concat([X, y], axis=1)
+
+    context.log_dataset(key, df=data, format=file_ext, index=False)
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/gen_class_data/latest/src/function.yaml b/functions/master/gen_class_data/latest/src/function.yaml index 4249bed5..1769bec0 100644 --- a/functions/master/gen_class_data/latest/src/function.yaml +++ b/functions/master/gen_class_data/latest/src/function.yaml @@ -1,57 +1,30 @@ -kind: job metadata: - name: gen-class-data - tag: '' - hash: 7759c5db6fd6a66e91351a10862cf5c09e2b59b3 - project: '' - labels: - author: Daniel categories: - - data-preparation + - data-generation + tag: '' + name: gen-class-data spec: - command: '' - args: [] - image: mlrun/mlrun - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo= - commands: [] - code_origin: http://github.com/aviaIguazio/functions.git#be04dfbae37aa7c2260ca800ffe248b38e34ebfc:/Users/Avi_Asulin/PycharmProjects/mlrun/functions/gen_class_data/gen_class_data.py - origin_filename: /Users/Avi_Asulin/PycharmProjects/mlrun/functions/gen_class_data/gen_class_data.py - requirements: [] + description: Create a binary classification sample dataset and save. + default_handler: gen_class_data entry_points: gen_class_data: - name: gen_class_data - doc: 'Create a binary classification sample dataset and save. - - If no filename is given it will default to: - - "simdata-{n_samples}X{m_features}.parquet". - - - Additional scikit-learn parameters can be set using **sk_params, please see - https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html - for more details.' + has_kwargs: false parameters: - name: context type: MLClientCtx doc: function context - default: '' - name: n_samples type: int doc: number of rows/samples - default: '' - name: m_features type: int doc: number of cols/features - default: '' - name: k_classes type: int doc: number of classes - default: '' - name: header type: Optional[List[str]] doc: header for features array - default: '' - name: label_column type: Optional[str] doc: column name of ground-truth series @@ -75,17 +48,25 @@ spec: - name: sk_params doc: additional parameters for `sklearn.datasets.make_classification` default: {} - outputs: - - default: '' lineno: 22 - description: Create a binary classification sample dataset and save. - default_handler: gen_class_data + doc: 'Create a binary classification sample dataset and save. + + If no filename is given it will default to: + + "simdata-{n_samples}X{m_features}.parquet". + + + Additional scikit-learn parameters can be set using **sk_params, please see + https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html + for more details.' + has_varargs: false + name: gen_class_data + command: '' disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} + image: mlrun/mlrun + build: + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo= + code_origin: '' +kind: job verbose: false diff --git a/functions/master/gen_class_data/latest/src/item.yaml b/functions/master/gen_class_data/latest/src/item.yaml index a965c0ab..a6dd94b6 100644 --- a/functions/master/gen_class_data/latest/src/item.yaml +++ b/functions/master/gen_class_data/latest/src/item.yaml @@ -1,6 +1,6 @@ apiVersion: v1 categories: -- data-preparation +- data-generation description: Create a binary classification sample dataset and save. doc: '' example: gen_class_data.ipynb @@ -11,7 +11,7 @@ labels: author: Daniel maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.1 +mlrunVersion: 1.7.0 name: gen_class_data platformVersion: 3.5.3 spec: @@ -21,4 +21,4 @@ spec: kind: job requirements: [] url: '' -version: 1.2.0 +version: 1.3.0 diff --git a/functions/master/gen_class_data/latest/static/documentation.html b/functions/master/gen_class_data/latest/static/documentation.html index 78d811f7..7125e01f 100644 --- a/functions/master/gen_class_data/latest/static/documentation.html +++ b/functions/master/gen_class_data/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/gen_class_data/latest/static/example.html b/functions/master/gen_class_data/latest/static/example.html index 0910cc44..853b6a66 100644 --- a/functions/master/gen_class_data/latest/static/example.html +++ b/functions/master/gen_class_data/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/gen_class_data/latest/static/function.html b/functions/master/gen_class_data/latest/static/function.html index a5a6f8c5..f4605fe7 100644 --- a/functions/master/gen_class_data/latest/static/function.html +++ b/functions/master/gen_class_data/latest/static/function.html @@ -28,60 +28,33 @@
         
-kind: job
 metadata:
-  name: gen-class-data
-  tag: ''
-  hash: 7759c5db6fd6a66e91351a10862cf5c09e2b59b3
-  project: ''
-  labels:
-    author: Daniel
   categories:
-  - data-preparation
+  - data-generation
+  tag: ''
+  name: gen-class-data
 spec:
-  command: ''
-  args: []
-  image: mlrun/mlrun
-  build:
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo=
-    commands: []
-    code_origin: http://github.com/aviaIguazio/functions.git#be04dfbae37aa7c2260ca800ffe248b38e34ebfc:/Users/Avi_Asulin/PycharmProjects/mlrun/functions/gen_class_data/gen_class_data.py
-    origin_filename: /Users/Avi_Asulin/PycharmProjects/mlrun/functions/gen_class_data/gen_class_data.py
-    requirements: []
+  description: Create a binary classification sample dataset and save.
+  default_handler: gen_class_data
   entry_points:
     gen_class_data:
-      name: gen_class_data
-      doc: 'Create a binary classification sample dataset and save.
-
-        If no filename is given it will default to:
-
-        "simdata-{n_samples}X{m_features}.parquet".
-
-
-        Additional scikit-learn parameters can be set using **sk_params, please see
-        https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html
-        for more details.'
+      has_kwargs: false
       parameters:
       - name: context
         type: MLClientCtx
         doc: function context
-        default: ''
       - name: n_samples
         type: int
         doc: number of rows/samples
-        default: ''
       - name: m_features
         type: int
         doc: number of cols/features
-        default: ''
       - name: k_classes
         type: int
         doc: number of classes
-        default: ''
       - name: header
         type: Optional[List[str]]
         doc: header for features array
-        default: ''
       - name: label_column
         type: Optional[str]
         doc: column name of ground-truth series
@@ -105,19 +78,27 @@
       - name: sk_params
         doc: additional parameters for `sklearn.datasets.make_classification`
         default: {}
-      outputs:
-      - default: ''
       lineno: 22
-  description: Create a binary classification sample dataset and save.
-  default_handler: gen_class_data
+      doc: 'Create a binary classification sample dataset and save.
+
+        If no filename is given it will default to:
+
+        "simdata-{n_samples}X{m_features}.parquet".
+
+
+        Additional scikit-learn parameters can be set using **sk_params, please see
+        https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html
+        for more details.'
+      has_varargs: false
+      name: gen_class_data
+  command: ''
   disable_auto_mount: false
-  clone_target_dir: ''
-  env: []
-  priority_class_name: ''
-  preemption_mode: prevent
-  affinity: null
-  tolerations: null
-  security_context: {}
+  image: mlrun/mlrun
+  build:
+    origin_filename: ''
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKaW1wb3J0IHBhbmRhcyBhcyBwZApmcm9tIHR5cGluZyBpbXBvcnQgT3B0aW9uYWwsIExpc3QKZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgpmcm9tIG1scnVuLmV4ZWN1dGlvbiBpbXBvcnQgTUxDbGllbnRDdHgKCgpkZWYgZ2VuX2NsYXNzX2RhdGEoCiAgICAgICAgY29udGV4dDogTUxDbGllbnRDdHgsCiAgICAgICAgbl9zYW1wbGVzOiBpbnQsCiAgICAgICAgbV9mZWF0dXJlczogaW50LAogICAgICAgIGtfY2xhc3NlczogaW50LAogICAgICAgIGhlYWRlcjogT3B0aW9uYWxbTGlzdFtzdHJdXSwKICAgICAgICBsYWJlbF9jb2x1bW46IE9wdGlvbmFsW3N0cl0gPSAibGFiZWxzIiwKICAgICAgICB3ZWlnaHQ6IGZsb2F0ID0gMC41LAogICAgICAgIHJhbmRvbV9zdGF0ZTogaW50ID0gMSwKICAgICAgICBrZXk6IHN0ciA9ICJjbGFzc2lmaWVyLWRhdGEiLAogICAgICAgIGZpbGVfZXh0OiBzdHIgPSAicGFycXVldCIsCiAgICAgICAgc2tfcGFyYW1zPXt9Cik6CiAgICAiIiJDcmVhdGUgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gc2FtcGxlIGRhdGFzZXQgYW5kIHNhdmUuCiAgICBJZiBubyBmaWxlbmFtZSBpcyBnaXZlbiBpdCB3aWxsIGRlZmF1bHQgdG86CiAgICAic2ltZGF0YS17bl9zYW1wbGVzfVh7bV9mZWF0dXJlc30ucGFycXVldCIuCgogICAgQWRkaXRpb25hbCBzY2lraXQtbGVhcm4gcGFyYW1ldGVycyBjYW4gYmUgc2V0IHVzaW5nICoqc2tfcGFyYW1zLCBwbGVhc2Ugc2VlIGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvbW9kdWxlcy9nZW5lcmF0ZWQvc2tsZWFybi5kYXRhc2V0cy5tYWtlX2NsYXNzaWZpY2F0aW9uLmh0bWwgZm9yIG1vcmUgZGV0YWlscy4KCiAgICA6cGFyYW0gY29udGV4dDogICAgICAgZnVuY3Rpb24gY29udGV4dAogICAgOnBhcmFtIG5fc2FtcGxlczogICAgIG51bWJlciBvZiByb3dzL3NhbXBsZXMKICAgIDpwYXJhbSBtX2ZlYXR1cmVzOiAgICBudW1iZXIgb2YgY29scy9mZWF0dXJlcwogICAgOnBhcmFtIGtfY2xhc3NlczogICAgIG51bWJlciBvZiBjbGFzc2VzCiAgICA6cGFyYW0gaGVhZGVyOiAgICAgICAgaGVhZGVyIGZvciBmZWF0dXJlcyBhcnJheQogICAgOnBhcmFtIGxhYmVsX2NvbHVtbjogIGNvbHVtbiBuYW1lIG9mIGdyb3VuZC10cnV0aCBzZXJpZXMKICAgIDpwYXJhbSB3ZWlnaHQ6ICAgICAgICBmcmFjdGlvbiBvZiBzYW1wbGUgbmVnYXRpdmUgdmFsdWUgKGdyb3VuZC10cnV0aD0wKQogICAgOnBhcmFtIHJhbmRvbV9zdGF0ZTogIHJuZyBzZWVkIChzZWUgaHR0cHM6Ly9zY2lraXQtbGVhcm4ub3JnL3N0YWJsZS9nbG9zc2FyeS5odG1sI3Rlcm0tcmFuZG9tLXN0YXRlKQogICAgOnBhcmFtIGtleTogICAgICAgICAgIGtleSBvZiBkYXRhIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gZmlsZV9leHQ6ICAgICAgKHBxdCkgZXh0ZW5zaW9uIGZvciBwYXJxdWV0IGZpbGUKICAgIDpwYXJhbSBza19wYXJhbXM6ICAgICBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgZm9yIGBza2xlYXJuLmRhdGFzZXRzLm1ha2VfY2xhc3NpZmljYXRpb25gCiAgICAiIiIKICAgIGZlYXR1cmVzLCBsYWJlbHMgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKAogICAgICAgIG5fc2FtcGxlcz1uX3NhbXBsZXMsCiAgICAgICAgbl9mZWF0dXJlcz1tX2ZlYXR1cmVzLAogICAgICAgIHdlaWdodHM9d2VpZ2h0LAogICAgICAgIG5fY2xhc3Nlcz1rX2NsYXNzZXMsCiAgICAgICAgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSwKICAgICAgICAqKnNrX3BhcmFtcykKCiAgICAjIG1ha2UgZGF0YWZyYW1lcywgYWRkIGNvbHVtbiBuYW1lcywgY29uY2F0ZW5hdGUgKFgsIHkpCiAgICBYID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzKQogICAgaWYgbm90IGhlYWRlcjoKICAgICAgICBYLmNvbHVtbnMgPSBbImZlYXRfIiArIHN0cih4KSBmb3IgeCBpbiByYW5nZShtX2ZlYXR1cmVzKV0KICAgIGVsc2U6CiAgICAgICAgWC5jb2x1bW5zID0gaGVhZGVyCgogICAgeSA9IHBkLkRhdGFGcmFtZShsYWJlbHMsIGNvbHVtbnM9W2xhYmVsX2NvbHVtbl0pCiAgICBkYXRhID0gcGQuY29uY2F0KFtYLCB5XSwgYXhpcz0xKQoKICAgIGNvbnRleHQubG9nX2RhdGFzZXQoa2V5LCBkZj1kYXRhLCBmb3JtYXQ9ZmlsZV9leHQsIGluZGV4PUZhbHNlKQo=
+    code_origin: ''
+kind: job
 verbose: false
 
         
diff --git a/functions/master/gen_class_data/latest/static/gen_class_data.html b/functions/master/gen_class_data/latest/static/gen_class_data.html
index 83113ae2..8641ff05 100644
--- a/functions/master/gen_class_data/latest/static/gen_class_data.html
+++ b/functions/master/gen_class_data/latest/static/gen_class_data.html
@@ -20,7 +20,7 @@
 
 
 
-
+
 
 
 
diff --git a/functions/master/gen_class_data/latest/static/item.html b/functions/master/gen_class_data/latest/static/item.html
index 3bd8626a..b1943f72 100644
--- a/functions/master/gen_class_data/latest/static/item.html
+++ b/functions/master/gen_class_data/latest/static/item.html
@@ -30,7 +30,7 @@
         
 apiVersion: v1
 categories:
-- data-preparation
+- data-generation
 description: Create a binary classification sample dataset and save.
 doc: ''
 example: gen_class_data.ipynb
@@ -41,7 +41,7 @@
   author: Daniel
 maintainers: []
 marketplaceType: ''
-mlrunVersion: 1.4.1
+mlrunVersion: 1.7.0
 name: gen_class_data
 platformVersion: 3.5.3
 spec:
@@ -51,7 +51,7 @@
   kind: job
   requirements: []
 url: ''
-version: 1.2.0
+version: 1.3.0
 
         
     
diff --git a/functions/master/github_utils/1.1.0/static/documentation.html b/functions/master/github_utils/1.1.0/static/documentation.html index f7179e88..6881d547 100644 --- a/functions/master/github_utils/1.1.0/static/documentation.html +++ b/functions/master/github_utils/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/github_utils/1.1.0/static/example.html b/functions/master/github_utils/1.1.0/static/example.html index 627030e0..f20d1623 100644 --- a/functions/master/github_utils/1.1.0/static/example.html +++ b/functions/master/github_utils/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/github_utils/1.1.0/static/github_utils.html b/functions/master/github_utils/1.1.0/static/github_utils.html index e1287336..a33bbcc8 100644 --- a/functions/master/github_utils/1.1.0/static/github_utils.html +++ b/functions/master/github_utils/1.1.0/static/github_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/github_utils/latest/static/documentation.html b/functions/master/github_utils/latest/static/documentation.html index f7179e88..6881d547 100644 --- a/functions/master/github_utils/latest/static/documentation.html +++ b/functions/master/github_utils/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/github_utils/latest/static/example.html b/functions/master/github_utils/latest/static/example.html index 627030e0..f20d1623 100644 --- a/functions/master/github_utils/latest/static/example.html +++ b/functions/master/github_utils/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/github_utils/latest/static/github_utils.html b/functions/master/github_utils/latest/static/github_utils.html index e1287336..a33bbcc8 100644 --- a/functions/master/github_utils/latest/static/github_utils.html +++ b/functions/master/github_utils/latest/static/github_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/1.1.0/src/function.yaml b/functions/master/hugging_face_serving/1.1.0/src/function.yaml index 764fc1cf..a628d7ab 100644 --- a/functions/master/hugging_face_serving/1.1.0/src/function.yaml +++ b/functions/master/hugging_face_serving/1.1.0/src/function.yaml @@ -1,46 +1,31 @@ -kind: serving metadata: name: hugging-face-serving - tag: '' - hash: 1a489a57da861f129eb26e933f34e58927e41195 - project: '' - labels: - author: yonish categories: - - huggingface - genai - model-serving - - machine-learning + tag: '' spec: - command: '' - args: [] + default_handler: '' + min_replicas: 1 + source: '' image: mlrun/ml-models build: functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmZyb20gYWJjIGltcG9ydCBBQkMKZnJvbSBpbXBvcnRsaWIgaW1wb3J0IGltcG9ydF9tb2R1bGUKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QKCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBwaXBlbGluZQoKaW1wb3J0IG1scnVuLnNlcnZpbmcKClBBQ0tBR0VfTU9EVUxFID0gInRyYW5zZm9ybWVycyIKU0VSSUFMSVpBQkxFX1RZUEVTID0gW2RpY3QsIGxpc3QsIHR1cGxlLCBzdHIsIGludCwgZmxvYXRdCgoKY2xhc3MgSHVnZ2luZ0ZhY2VNb2RlbFNlcnZlcihtbHJ1bi5zZXJ2aW5nLlYyTW9kZWxTZXJ2ZXIsIEFCQyk6CiAgICAiIiIKICAgIEh1Z2dpbmcgRmFjZSBNb2RlbCBzZXJ2aW5nIGNsYXNzLCBpbmhlcml0aW5nIHRoZSBWMk1vZGVsU2VydmVyIGNsYXNzIGZvciBiZWluZyBpbml0aWFsaXplZCBhdXRvbWF0aWNhbGx5IGJ5IHRoZQogICAgbW9kZWwgc2VydmVyIGFuZCBiZSBhYmxlIHRvIHJ1biBsb2NhbGx5IGFzIHBhcnQgb2YgYSBudWNsaW8gc2VydmVybGVzcyBmdW5jdGlvbiwgb3IgYXMgcGFydCBvZiBhIHJlYWwtdGltZSBwaXBlbGluZS4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgICAgIG5hbWU6IHN0ciwKICAgICAgICB0YXNrOiBzdHIsCiAgICAgICAgbW9kZWxfcGF0aDogc3RyID0gTm9uZSwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG1vZGVsX2NsYXNzOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9jbGFzczogc3RyID0gTm9uZSwKICAgICAgICBmcmFtZXdvcms6IHN0ciA9IE5vbmUsCiAgICAgICAgKipjbGFzc19hcmdzLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIGEgc2VydmluZyBjbGFzcyBmb3IgYSBIdWdnaW5nIGZhY2UgbW9kZWwuCgogICAgICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgIFRoZSBtbHJ1biBjb250ZXh0IHRvIHdvcmsgd2l0aAogICAgICAgIDpwYXJhbSBuYW1lOiAgICAgICAgICAgIFRoZSBuYW1lIG9mIHRoaXMgc2VydmVyIHRvIGJlIGluaXRpYWxpemVkCiAgICAgICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgTm90IGluIHVzZS4gV2hlbiBhZGRpbmcgYSBtb2RlbCBwYXNzIGFueSBzdHJpbmcgdmFsdWUKICAgICAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICBUaGUgbW9kZWwncyBuYW1lIGluIHRoZSBIdWdnaW5nIEZhY2UgaHViCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYG5scHRvd24vYmVydC1iYXNlLW11bHRpbGluZ3VhbC11bmNhc2VkLXNlbnRpbWVudGAKICAgICAgICA6cGFyYW0gbW9kZWxfY2xhc3M6ICAgICBUaGUgbW9kZWwncyBjbGFzcyB0eXBlIG9iamVjdCB3aGljaCBjYW4gYmUgcGFzc2VkIGFzIHRoZSBjbGFzcydzIG5hbWUgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTXVzdCBiZSBwcm92aWRlZCBhbmQgdG8gYmUgbWF0Y2hlZCB3aXRoIGBtb2RlbF9uYW1lYC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlLmcuLCBgQXV0b01vZGVsRm9yU2VxdWVuY2VDbGFzc2lmaWNhdGlvbmAKICAgICAgICA6cGFyYW0gdG9rZW5pemVyX25hbWU6ICBUaGUgdG9rZW5pemVyJ3MgbmFtZSBpbiB0aGUgSHVnZ2luZyBGYWNlIGh1YgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4sIGBubHB0b3duL2JlcnQtYmFzZS1tdWx0aWxpbmd1YWwtdW5jYXNlZC1zZW50aW1lbnRgCiAgICAgICAgOnBhcmFtIHRva2VuaXplcl9jbGFzczogVGhlIG1vZGVsJ3MgY2xhc3MgdHlwZSBvYmplY3Qgd2hpY2ggY2FuIGJlIHBhc3NlZCBhcyB0aGUgY2xhc3MncyBuYW1lIChzdHJpbmcpLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgYmUgcHJvdmlkZWQgYW5kIHRvIGJlIG1hdGNoZWQgd2l0aCBgbW9kZWxfbmFtZWAuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYEF1dG9Ub2tlbml6ZXJgCiAgICAgICAgOnBhcmFtIGZyYW1ld29yazogICAgICAgVGhlIGZyYW1ld29yayB0byB1c2UsIGVpdGhlciBgInB0ImAgZm9yIFB5VG9yY2ggb3IgYCJ0ZiJgIGZvciBUZW5zb3JGbG93LiBUaGUgc3BlY2lmaWVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWV3b3JrIG11c3QgYmUgaW5zdGFsbGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vIGZyYW1ld29yayBpcyBzcGVjaWZpZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUgb25lIGN1cnJlbnRseSBpbnN0YWxsZWQuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgbm8gZnJhbWV3b3JrIGlzIHNwZWNpZmllZCBhbmQgYm90aCBmcmFtZXdvcmtzIGFyZSBpbnN0YWxsZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZXdvcmsgb2YgdGhlIGBtb2RlbGAsIG9yIHRvIFB5VG9yY2ggaWYgbm8gbW9kZWwgaXMgcHJvdmlkZWQuCiAgICAgICAgOnBhcmFtIGNsYXNzX2FyZ3M6ICAgICAgLQogICAgICAgICIiIgogICAgICAgIHN1cGVyKEh1Z2dpbmdGYWNlTW9kZWxTZXJ2ZXIsIHNlbGYpLl9faW5pdF9fKAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIG5hbWU9bmFtZSwKICAgICAgICAgICAgbW9kZWxfcGF0aD1tb2RlbF9wYXRoLAogICAgICAgICAgICAqKmNsYXNzX2FyZ3MsCiAgICAgICAgKQogICAgICAgIHNlbGYudGFzayA9IHRhc2sKICAgICAgICBzZWxmLm1vZGVsID0gTm9uZQogICAgICAgIHNlbGYudG9rZW5pemVyID0gTm9uZQogICAgICAgIHNlbGYubW9kZWxfbmFtZSA9IG1vZGVsX25hbWUKICAgICAgICBzZWxmLnRva2VuaXplcl9uYW1lID0gdG9rZW5pemVyX25hbWUKICAgICAgICBzZWxmLm1vZGVsX2NsYXNzID0gbW9kZWxfY2xhc3MKICAgICAgICBzZWxmLnRva2VuaXplcl9jbGFzcyA9IHRva2VuaXplcl9jbGFzcwogICAgICAgIHNlbGYuZnJhbWV3b3JrID0gZnJhbWV3b3JrCiAgICAgICAgc2VsZi5waXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYpOgogICAgICAgICIiImxvYWQgYW5kIGluaXRpYWxpemUgdGhlIG1vZGVsIGFuZC9vciBvdGhlciBlbGVtZW50cyIiIgogICAgICAgIGlmIHNlbGYubW9kZWxfY2xhc3M6CiAgICAgICAgICAgIG1vZGVsX29iamVjdCA9IGdldGF0dHIoaW1wb3J0X21vZHVsZShQQUNLQUdFX01PRFVMRSksIHNlbGYubW9kZWxfY2xhc3MpCiAgICAgICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbF9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYubW9kZWxfbmFtZSkKICAgICAgICBpZiBzZWxmLnRva2VuaXplcl9jbGFzczoKICAgICAgICAgICAgdG9rZW5pemVyX29iamVjdCA9IGdldGF0dHIoCiAgICAgICAgICAgICAgICBpbXBvcnRfbW9kdWxlKFBBQ0tBR0VfTU9EVUxFKSwgc2VsZi50b2tlbml6ZXJfY2xhc3MKICAgICAgICAgICAgKQogICAgICAgICAgICBzZWxmLnRva2VuaXplciA9IHRva2VuaXplcl9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYudG9rZW5pemVyX25hbWUpCiAgICAgICAgc2VsZi5waXBlID0gcGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9c2VsZi50YXNrLAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsIG9yIHNlbGYubW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPXNlbGYudG9rZW5pemVyLAogICAgICAgICAgICBmcmFtZXdvcms9c2VsZi5mcmFtZXdvcmssCiAgICAgICAgKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIGJvZHk6IGRpY3QpIC0+IExpc3Q6CiAgICAgICAgIiIiR2VuZXJhdGUgbW9kZWwgcHJlZGljdGlvbnMgZnJvbSBzYW1wbGUuIiIiCiAgICAgICAgaWYgc2VsZi5waXBlIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSB1c2UgYC5sb2FkKClgIikKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoYm9keVsiaW5wdXRzIl1bMF0sIGRpY3QpOgogICAgICAgICAgICAgICAgcmVzdWx0ID0gW3NlbGYucGlwZSgqKl9pbnB1dCkgZm9yIF9pbnB1dCBpbiBib2R5WyJpbnB1dHMiXV0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHNlbGYucGlwZShib2R5WyJpbnB1dHMiXSkKICAgICAgICAgICAgIyByZXBsYWNlIGxpc3Qgb2YgbGlzdHMgb2YgZGljdHMgaW50byBhIGxpc3Qgb2YgZGljdHM6CiAgICAgICAgICAgIGlmIGFsbChpc2luc3RhbmNlKHJlcywgbGlzdCkgZm9yIHJlcyBpbiByZXN1bHQpOgogICAgICAgICAgICAgICAgbmV3X3Jlc3VsdCA9IFtyZXNbMF0gZm9yIHJlcyBpbiByZXN1bHRdCiAgICAgICAgICAgICAgICByZXN1bHQgPSBuZXdfcmVzdWx0CgogICAgICAgICAgICBub25fc2VyaWFsaXphYmxlX3R5cGVzID0gW10KICAgICAgICAgICAgZm9yIHJlcyBpbiByZXN1bHQ6CiAgICAgICAgICAgICAgICBmb3Iga2V5LCB2YWwgaW4gcmVzLml0ZW1zKCk6CiAgICAgICAgICAgICAgICAgICAgaWYgdHlwZSh2YWwpIG5vdCBpbiBTRVJJQUxJWkFCTEVfVFlQRVM6CiAgICAgICAgICAgICAgICAgICAgICAgIG5vbl9zZXJpYWxpemFibGVfdHlwZXMuYXBwZW5kKHN0cih0eXBlKHZhbCkpKQogICAgICAgICAgICAgICAgICAgICAgICByZXNba2V5XSA9IHN0cih2YWwpCiAgICAgICAgICAgIGlmIG5vbl9zZXJpYWxpemFibGVfdHlwZXM6CiAgICAgICAgICAgICAgICBzZWxmLmNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJOb24tc2VyaWFsaXphYmxlIHR5cGVzOiB7bm9uX3NlcmlhbGl6YWJsZV90eXBlc30gd2VyZSBjYXN0ZWQgdG8gc3RyaW5ncyIKICAgICAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbigiRmFpbGVkIHRvIHByZWRpY3QgJXMiICUgZSkKICAgICAgICByZXR1cm4gcmVzdWx0Cgpmcm9tIG1scnVuLnJ1bnRpbWVzIGltcG9ydCBudWNsaW9faW5pdF9ob29rCmRlZiBpbml0X2NvbnRleHQoY29udGV4dCk6CiAgICBudWNsaW9faW5pdF9ob29rKGNvbnRleHQsIGdsb2JhbHMoKSwgJ3NlcnZpbmdfdjInKQoKZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuIGNvbnRleHQubWxydW5faGFuZGxlcihjb250ZXh0LCBldmVudCkK - commands: [] code_origin: '' origin_filename: '' requirements: - transformers==4.21.3 - tensorflow==2.9.2 - description: Generic Hugging Face model server. - default_handler: '' + function_kind: serving_v2 + default_class: HuggingFaceModelServer + base_image_pull: false + max_replicas: 4 + command: '' disable_auto_mount: false - clone_target_dir: '' + function_handler: hugging-face-serving-nuclio:handler + description: Generic Hugging Face model server. env: - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK value: enabled - priority_class_name: '' - preemption_mode: prevent - min_replicas: 1 - max_replicas: 4 - source: '' - function_kind: serving_v2 - function_handler: hugging_face_serving:handler - base_image_pull: false - default_class: HuggingFaceModelServer - secret_sources: [] - affinity: null - tolerations: null - security_context: {} verbose: false +kind: serving diff --git a/functions/master/hugging_face_serving/1.1.0/src/item.yaml b/functions/master/hugging_face_serving/1.1.0/src/item.yaml index d1f78769..48b063e4 100644 --- a/functions/master/hugging_face_serving/1.1.0/src/item.yaml +++ b/functions/master/hugging_face_serving/1.1.0/src/item.yaml @@ -1,9 +1,7 @@ apiVersion: v1 categories: -- huggingface - genai - model-serving -- machine-learning description: Generic Hugging Face model server. doc: '' example: hugging_face_serving.ipynb diff --git a/functions/master/hugging_face_serving/1.1.0/static/documentation.html b/functions/master/hugging_face_serving/1.1.0/static/documentation.html index a95b606c..65c7aeb8 100644 --- a/functions/master/hugging_face_serving/1.1.0/static/documentation.html +++ b/functions/master/hugging_face_serving/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/1.1.0/static/example.html b/functions/master/hugging_face_serving/1.1.0/static/example.html index bc408d54..20ac581a 100644 --- a/functions/master/hugging_face_serving/1.1.0/static/example.html +++ b/functions/master/hugging_face_serving/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/1.1.0/static/function.html b/functions/master/hugging_face_serving/1.1.0/static/function.html index ef13ff66..c30517c4 100644 --- a/functions/master/hugging_face_serving/1.1.0/static/function.html +++ b/functions/master/hugging_face_serving/1.1.0/static/function.html @@ -28,52 +28,37 @@
         
-kind: serving
 metadata:
   name: hugging-face-serving
-  tag: ''
-  hash: 1a489a57da861f129eb26e933f34e58927e41195
-  project: ''
-  labels:
-    author: yonish
   categories:
-  - huggingface
   - genai
   - model-serving
-  - machine-learning
+  tag: ''
 spec:
-  command: ''
-  args: []
+  default_handler: ''
+  min_replicas: 1
+  source: ''
   image: mlrun/ml-models
   build:
     functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmZyb20gYWJjIGltcG9ydCBBQkMKZnJvbSBpbXBvcnRsaWIgaW1wb3J0IGltcG9ydF9tb2R1bGUKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QKCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBwaXBlbGluZQoKaW1wb3J0IG1scnVuLnNlcnZpbmcKClBBQ0tBR0VfTU9EVUxFID0gInRyYW5zZm9ybWVycyIKU0VSSUFMSVpBQkxFX1RZUEVTID0gW2RpY3QsIGxpc3QsIHR1cGxlLCBzdHIsIGludCwgZmxvYXRdCgoKY2xhc3MgSHVnZ2luZ0ZhY2VNb2RlbFNlcnZlcihtbHJ1bi5zZXJ2aW5nLlYyTW9kZWxTZXJ2ZXIsIEFCQyk6CiAgICAiIiIKICAgIEh1Z2dpbmcgRmFjZSBNb2RlbCBzZXJ2aW5nIGNsYXNzLCBpbmhlcml0aW5nIHRoZSBWMk1vZGVsU2VydmVyIGNsYXNzIGZvciBiZWluZyBpbml0aWFsaXplZCBhdXRvbWF0aWNhbGx5IGJ5IHRoZQogICAgbW9kZWwgc2VydmVyIGFuZCBiZSBhYmxlIHRvIHJ1biBsb2NhbGx5IGFzIHBhcnQgb2YgYSBudWNsaW8gc2VydmVybGVzcyBmdW5jdGlvbiwgb3IgYXMgcGFydCBvZiBhIHJlYWwtdGltZSBwaXBlbGluZS4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgICAgIG5hbWU6IHN0ciwKICAgICAgICB0YXNrOiBzdHIsCiAgICAgICAgbW9kZWxfcGF0aDogc3RyID0gTm9uZSwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG1vZGVsX2NsYXNzOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9jbGFzczogc3RyID0gTm9uZSwKICAgICAgICBmcmFtZXdvcms6IHN0ciA9IE5vbmUsCiAgICAgICAgKipjbGFzc19hcmdzLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIGEgc2VydmluZyBjbGFzcyBmb3IgYSBIdWdnaW5nIGZhY2UgbW9kZWwuCgogICAgICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgIFRoZSBtbHJ1biBjb250ZXh0IHRvIHdvcmsgd2l0aAogICAgICAgIDpwYXJhbSBuYW1lOiAgICAgICAgICAgIFRoZSBuYW1lIG9mIHRoaXMgc2VydmVyIHRvIGJlIGluaXRpYWxpemVkCiAgICAgICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgTm90IGluIHVzZS4gV2hlbiBhZGRpbmcgYSBtb2RlbCBwYXNzIGFueSBzdHJpbmcgdmFsdWUKICAgICAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICBUaGUgbW9kZWwncyBuYW1lIGluIHRoZSBIdWdnaW5nIEZhY2UgaHViCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYG5scHRvd24vYmVydC1iYXNlLW11bHRpbGluZ3VhbC11bmNhc2VkLXNlbnRpbWVudGAKICAgICAgICA6cGFyYW0gbW9kZWxfY2xhc3M6ICAgICBUaGUgbW9kZWwncyBjbGFzcyB0eXBlIG9iamVjdCB3aGljaCBjYW4gYmUgcGFzc2VkIGFzIHRoZSBjbGFzcydzIG5hbWUgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTXVzdCBiZSBwcm92aWRlZCBhbmQgdG8gYmUgbWF0Y2hlZCB3aXRoIGBtb2RlbF9uYW1lYC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlLmcuLCBgQXV0b01vZGVsRm9yU2VxdWVuY2VDbGFzc2lmaWNhdGlvbmAKICAgICAgICA6cGFyYW0gdG9rZW5pemVyX25hbWU6ICBUaGUgdG9rZW5pemVyJ3MgbmFtZSBpbiB0aGUgSHVnZ2luZyBGYWNlIGh1YgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4sIGBubHB0b3duL2JlcnQtYmFzZS1tdWx0aWxpbmd1YWwtdW5jYXNlZC1zZW50aW1lbnRgCiAgICAgICAgOnBhcmFtIHRva2VuaXplcl9jbGFzczogVGhlIG1vZGVsJ3MgY2xhc3MgdHlwZSBvYmplY3Qgd2hpY2ggY2FuIGJlIHBhc3NlZCBhcyB0aGUgY2xhc3MncyBuYW1lIChzdHJpbmcpLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgYmUgcHJvdmlkZWQgYW5kIHRvIGJlIG1hdGNoZWQgd2l0aCBgbW9kZWxfbmFtZWAuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYEF1dG9Ub2tlbml6ZXJgCiAgICAgICAgOnBhcmFtIGZyYW1ld29yazogICAgICAgVGhlIGZyYW1ld29yayB0byB1c2UsIGVpdGhlciBgInB0ImAgZm9yIFB5VG9yY2ggb3IgYCJ0ZiJgIGZvciBUZW5zb3JGbG93LiBUaGUgc3BlY2lmaWVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWV3b3JrIG11c3QgYmUgaW5zdGFsbGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vIGZyYW1ld29yayBpcyBzcGVjaWZpZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUgb25lIGN1cnJlbnRseSBpbnN0YWxsZWQuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgbm8gZnJhbWV3b3JrIGlzIHNwZWNpZmllZCBhbmQgYm90aCBmcmFtZXdvcmtzIGFyZSBpbnN0YWxsZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZXdvcmsgb2YgdGhlIGBtb2RlbGAsIG9yIHRvIFB5VG9yY2ggaWYgbm8gbW9kZWwgaXMgcHJvdmlkZWQuCiAgICAgICAgOnBhcmFtIGNsYXNzX2FyZ3M6ICAgICAgLQogICAgICAgICIiIgogICAgICAgIHN1cGVyKEh1Z2dpbmdGYWNlTW9kZWxTZXJ2ZXIsIHNlbGYpLl9faW5pdF9fKAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIG5hbWU9bmFtZSwKICAgICAgICAgICAgbW9kZWxfcGF0aD1tb2RlbF9wYXRoLAogICAgICAgICAgICAqKmNsYXNzX2FyZ3MsCiAgICAgICAgKQogICAgICAgIHNlbGYudGFzayA9IHRhc2sKICAgICAgICBzZWxmLm1vZGVsID0gTm9uZQogICAgICAgIHNlbGYudG9rZW5pemVyID0gTm9uZQogICAgICAgIHNlbGYubW9kZWxfbmFtZSA9IG1vZGVsX25hbWUKICAgICAgICBzZWxmLnRva2VuaXplcl9uYW1lID0gdG9rZW5pemVyX25hbWUKICAgICAgICBzZWxmLm1vZGVsX2NsYXNzID0gbW9kZWxfY2xhc3MKICAgICAgICBzZWxmLnRva2VuaXplcl9jbGFzcyA9IHRva2VuaXplcl9jbGFzcwogICAgICAgIHNlbGYuZnJhbWV3b3JrID0gZnJhbWV3b3JrCiAgICAgICAgc2VsZi5waXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYpOgogICAgICAgICIiImxvYWQgYW5kIGluaXRpYWxpemUgdGhlIG1vZGVsIGFuZC9vciBvdGhlciBlbGVtZW50cyIiIgogICAgICAgIGlmIHNlbGYubW9kZWxfY2xhc3M6CiAgICAgICAgICAgIG1vZGVsX29iamVjdCA9IGdldGF0dHIoaW1wb3J0X21vZHVsZShQQUNLQUdFX01PRFVMRSksIHNlbGYubW9kZWxfY2xhc3MpCiAgICAgICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbF9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYubW9kZWxfbmFtZSkKICAgICAgICBpZiBzZWxmLnRva2VuaXplcl9jbGFzczoKICAgICAgICAgICAgdG9rZW5pemVyX29iamVjdCA9IGdldGF0dHIoCiAgICAgICAgICAgICAgICBpbXBvcnRfbW9kdWxlKFBBQ0tBR0VfTU9EVUxFKSwgc2VsZi50b2tlbml6ZXJfY2xhc3MKICAgICAgICAgICAgKQogICAgICAgICAgICBzZWxmLnRva2VuaXplciA9IHRva2VuaXplcl9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYudG9rZW5pemVyX25hbWUpCiAgICAgICAgc2VsZi5waXBlID0gcGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9c2VsZi50YXNrLAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsIG9yIHNlbGYubW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPXNlbGYudG9rZW5pemVyLAogICAgICAgICAgICBmcmFtZXdvcms9c2VsZi5mcmFtZXdvcmssCiAgICAgICAgKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIGJvZHk6IGRpY3QpIC0+IExpc3Q6CiAgICAgICAgIiIiR2VuZXJhdGUgbW9kZWwgcHJlZGljdGlvbnMgZnJvbSBzYW1wbGUuIiIiCiAgICAgICAgaWYgc2VsZi5waXBlIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSB1c2UgYC5sb2FkKClgIikKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoYm9keVsiaW5wdXRzIl1bMF0sIGRpY3QpOgogICAgICAgICAgICAgICAgcmVzdWx0ID0gW3NlbGYucGlwZSgqKl9pbnB1dCkgZm9yIF9pbnB1dCBpbiBib2R5WyJpbnB1dHMiXV0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHNlbGYucGlwZShib2R5WyJpbnB1dHMiXSkKICAgICAgICAgICAgIyByZXBsYWNlIGxpc3Qgb2YgbGlzdHMgb2YgZGljdHMgaW50byBhIGxpc3Qgb2YgZGljdHM6CiAgICAgICAgICAgIGlmIGFsbChpc2luc3RhbmNlKHJlcywgbGlzdCkgZm9yIHJlcyBpbiByZXN1bHQpOgogICAgICAgICAgICAgICAgbmV3X3Jlc3VsdCA9IFtyZXNbMF0gZm9yIHJlcyBpbiByZXN1bHRdCiAgICAgICAgICAgICAgICByZXN1bHQgPSBuZXdfcmVzdWx0CgogICAgICAgICAgICBub25fc2VyaWFsaXphYmxlX3R5cGVzID0gW10KICAgICAgICAgICAgZm9yIHJlcyBpbiByZXN1bHQ6CiAgICAgICAgICAgICAgICBmb3Iga2V5LCB2YWwgaW4gcmVzLml0ZW1zKCk6CiAgICAgICAgICAgICAgICAgICAgaWYgdHlwZSh2YWwpIG5vdCBpbiBTRVJJQUxJWkFCTEVfVFlQRVM6CiAgICAgICAgICAgICAgICAgICAgICAgIG5vbl9zZXJpYWxpemFibGVfdHlwZXMuYXBwZW5kKHN0cih0eXBlKHZhbCkpKQogICAgICAgICAgICAgICAgICAgICAgICByZXNba2V5XSA9IHN0cih2YWwpCiAgICAgICAgICAgIGlmIG5vbl9zZXJpYWxpemFibGVfdHlwZXM6CiAgICAgICAgICAgICAgICBzZWxmLmNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJOb24tc2VyaWFsaXphYmxlIHR5cGVzOiB7bm9uX3NlcmlhbGl6YWJsZV90eXBlc30gd2VyZSBjYXN0ZWQgdG8gc3RyaW5ncyIKICAgICAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbigiRmFpbGVkIHRvIHByZWRpY3QgJXMiICUgZSkKICAgICAgICByZXR1cm4gcmVzdWx0Cgpmcm9tIG1scnVuLnJ1bnRpbWVzIGltcG9ydCBudWNsaW9faW5pdF9ob29rCmRlZiBpbml0X2NvbnRleHQoY29udGV4dCk6CiAgICBudWNsaW9faW5pdF9ob29rKGNvbnRleHQsIGdsb2JhbHMoKSwgJ3NlcnZpbmdfdjInKQoKZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuIGNvbnRleHQubWxydW5faGFuZGxlcihjb250ZXh0LCBldmVudCkK
-    commands: []
     code_origin: ''
     origin_filename: ''
     requirements:
     - transformers==4.21.3
     - tensorflow==2.9.2
-  description: Generic Hugging Face model server.
-  default_handler: ''
+  function_kind: serving_v2
+  default_class: HuggingFaceModelServer
+  base_image_pull: false
+  max_replicas: 4
+  command: ''
   disable_auto_mount: false
-  clone_target_dir: ''
+  function_handler: hugging-face-serving-nuclio:handler
+  description: Generic Hugging Face model server.
   env:
   - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK
     value: enabled
-  priority_class_name: ''
-  preemption_mode: prevent
-  min_replicas: 1
-  max_replicas: 4
-  source: ''
-  function_kind: serving_v2
-  function_handler: hugging_face_serving:handler
-  base_image_pull: false
-  default_class: HuggingFaceModelServer
-  secret_sources: []
-  affinity: null
-  tolerations: null
-  security_context: {}
 verbose: false
+kind: serving
 
         
     
diff --git a/functions/master/hugging_face_serving/1.1.0/static/hugging_face_serving.html b/functions/master/hugging_face_serving/1.1.0/static/hugging_face_serving.html index b07151ed..848cc67e 100644 --- a/functions/master/hugging_face_serving/1.1.0/static/hugging_face_serving.html +++ b/functions/master/hugging_face_serving/1.1.0/static/hugging_face_serving.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/1.1.0/static/item.html b/functions/master/hugging_face_serving/1.1.0/static/item.html index 62cc1b5b..784df913 100644 --- a/functions/master/hugging_face_serving/1.1.0/static/item.html +++ b/functions/master/hugging_face_serving/1.1.0/static/item.html @@ -30,10 +30,8 @@ apiVersion: v1 categories: -- huggingface - genai - model-serving -- machine-learning description: Generic Hugging Face model server. doc: '' example: hugging_face_serving.ipynb diff --git a/functions/master/hugging_face_serving/latest/src/function.yaml b/functions/master/hugging_face_serving/latest/src/function.yaml index 764fc1cf..a628d7ab 100644 --- a/functions/master/hugging_face_serving/latest/src/function.yaml +++ b/functions/master/hugging_face_serving/latest/src/function.yaml @@ -1,46 +1,31 @@ -kind: serving metadata: name: hugging-face-serving - tag: '' - hash: 1a489a57da861f129eb26e933f34e58927e41195 - project: '' - labels: - author: yonish categories: - - huggingface - genai - model-serving - - machine-learning + tag: '' spec: - command: '' - args: [] + default_handler: '' + min_replicas: 1 + source: '' image: mlrun/ml-models build: functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmZyb20gYWJjIGltcG9ydCBBQkMKZnJvbSBpbXBvcnRsaWIgaW1wb3J0IGltcG9ydF9tb2R1bGUKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QKCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBwaXBlbGluZQoKaW1wb3J0IG1scnVuLnNlcnZpbmcKClBBQ0tBR0VfTU9EVUxFID0gInRyYW5zZm9ybWVycyIKU0VSSUFMSVpBQkxFX1RZUEVTID0gW2RpY3QsIGxpc3QsIHR1cGxlLCBzdHIsIGludCwgZmxvYXRdCgoKY2xhc3MgSHVnZ2luZ0ZhY2VNb2RlbFNlcnZlcihtbHJ1bi5zZXJ2aW5nLlYyTW9kZWxTZXJ2ZXIsIEFCQyk6CiAgICAiIiIKICAgIEh1Z2dpbmcgRmFjZSBNb2RlbCBzZXJ2aW5nIGNsYXNzLCBpbmhlcml0aW5nIHRoZSBWMk1vZGVsU2VydmVyIGNsYXNzIGZvciBiZWluZyBpbml0aWFsaXplZCBhdXRvbWF0aWNhbGx5IGJ5IHRoZQogICAgbW9kZWwgc2VydmVyIGFuZCBiZSBhYmxlIHRvIHJ1biBsb2NhbGx5IGFzIHBhcnQgb2YgYSBudWNsaW8gc2VydmVybGVzcyBmdW5jdGlvbiwgb3IgYXMgcGFydCBvZiBhIHJlYWwtdGltZSBwaXBlbGluZS4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgICAgIG5hbWU6IHN0ciwKICAgICAgICB0YXNrOiBzdHIsCiAgICAgICAgbW9kZWxfcGF0aDogc3RyID0gTm9uZSwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG1vZGVsX2NsYXNzOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9jbGFzczogc3RyID0gTm9uZSwKICAgICAgICBmcmFtZXdvcms6IHN0ciA9IE5vbmUsCiAgICAgICAgKipjbGFzc19hcmdzLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIGEgc2VydmluZyBjbGFzcyBmb3IgYSBIdWdnaW5nIGZhY2UgbW9kZWwuCgogICAgICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgIFRoZSBtbHJ1biBjb250ZXh0IHRvIHdvcmsgd2l0aAogICAgICAgIDpwYXJhbSBuYW1lOiAgICAgICAgICAgIFRoZSBuYW1lIG9mIHRoaXMgc2VydmVyIHRvIGJlIGluaXRpYWxpemVkCiAgICAgICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgTm90IGluIHVzZS4gV2hlbiBhZGRpbmcgYSBtb2RlbCBwYXNzIGFueSBzdHJpbmcgdmFsdWUKICAgICAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICBUaGUgbW9kZWwncyBuYW1lIGluIHRoZSBIdWdnaW5nIEZhY2UgaHViCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYG5scHRvd24vYmVydC1iYXNlLW11bHRpbGluZ3VhbC11bmNhc2VkLXNlbnRpbWVudGAKICAgICAgICA6cGFyYW0gbW9kZWxfY2xhc3M6ICAgICBUaGUgbW9kZWwncyBjbGFzcyB0eXBlIG9iamVjdCB3aGljaCBjYW4gYmUgcGFzc2VkIGFzIHRoZSBjbGFzcydzIG5hbWUgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTXVzdCBiZSBwcm92aWRlZCBhbmQgdG8gYmUgbWF0Y2hlZCB3aXRoIGBtb2RlbF9uYW1lYC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlLmcuLCBgQXV0b01vZGVsRm9yU2VxdWVuY2VDbGFzc2lmaWNhdGlvbmAKICAgICAgICA6cGFyYW0gdG9rZW5pemVyX25hbWU6ICBUaGUgdG9rZW5pemVyJ3MgbmFtZSBpbiB0aGUgSHVnZ2luZyBGYWNlIGh1YgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4sIGBubHB0b3duL2JlcnQtYmFzZS1tdWx0aWxpbmd1YWwtdW5jYXNlZC1zZW50aW1lbnRgCiAgICAgICAgOnBhcmFtIHRva2VuaXplcl9jbGFzczogVGhlIG1vZGVsJ3MgY2xhc3MgdHlwZSBvYmplY3Qgd2hpY2ggY2FuIGJlIHBhc3NlZCBhcyB0aGUgY2xhc3MncyBuYW1lIChzdHJpbmcpLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgYmUgcHJvdmlkZWQgYW5kIHRvIGJlIG1hdGNoZWQgd2l0aCBgbW9kZWxfbmFtZWAuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYEF1dG9Ub2tlbml6ZXJgCiAgICAgICAgOnBhcmFtIGZyYW1ld29yazogICAgICAgVGhlIGZyYW1ld29yayB0byB1c2UsIGVpdGhlciBgInB0ImAgZm9yIFB5VG9yY2ggb3IgYCJ0ZiJgIGZvciBUZW5zb3JGbG93LiBUaGUgc3BlY2lmaWVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWV3b3JrIG11c3QgYmUgaW5zdGFsbGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vIGZyYW1ld29yayBpcyBzcGVjaWZpZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUgb25lIGN1cnJlbnRseSBpbnN0YWxsZWQuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgbm8gZnJhbWV3b3JrIGlzIHNwZWNpZmllZCBhbmQgYm90aCBmcmFtZXdvcmtzIGFyZSBpbnN0YWxsZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZXdvcmsgb2YgdGhlIGBtb2RlbGAsIG9yIHRvIFB5VG9yY2ggaWYgbm8gbW9kZWwgaXMgcHJvdmlkZWQuCiAgICAgICAgOnBhcmFtIGNsYXNzX2FyZ3M6ICAgICAgLQogICAgICAgICIiIgogICAgICAgIHN1cGVyKEh1Z2dpbmdGYWNlTW9kZWxTZXJ2ZXIsIHNlbGYpLl9faW5pdF9fKAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIG5hbWU9bmFtZSwKICAgICAgICAgICAgbW9kZWxfcGF0aD1tb2RlbF9wYXRoLAogICAgICAgICAgICAqKmNsYXNzX2FyZ3MsCiAgICAgICAgKQogICAgICAgIHNlbGYudGFzayA9IHRhc2sKICAgICAgICBzZWxmLm1vZGVsID0gTm9uZQogICAgICAgIHNlbGYudG9rZW5pemVyID0gTm9uZQogICAgICAgIHNlbGYubW9kZWxfbmFtZSA9IG1vZGVsX25hbWUKICAgICAgICBzZWxmLnRva2VuaXplcl9uYW1lID0gdG9rZW5pemVyX25hbWUKICAgICAgICBzZWxmLm1vZGVsX2NsYXNzID0gbW9kZWxfY2xhc3MKICAgICAgICBzZWxmLnRva2VuaXplcl9jbGFzcyA9IHRva2VuaXplcl9jbGFzcwogICAgICAgIHNlbGYuZnJhbWV3b3JrID0gZnJhbWV3b3JrCiAgICAgICAgc2VsZi5waXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYpOgogICAgICAgICIiImxvYWQgYW5kIGluaXRpYWxpemUgdGhlIG1vZGVsIGFuZC9vciBvdGhlciBlbGVtZW50cyIiIgogICAgICAgIGlmIHNlbGYubW9kZWxfY2xhc3M6CiAgICAgICAgICAgIG1vZGVsX29iamVjdCA9IGdldGF0dHIoaW1wb3J0X21vZHVsZShQQUNLQUdFX01PRFVMRSksIHNlbGYubW9kZWxfY2xhc3MpCiAgICAgICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbF9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYubW9kZWxfbmFtZSkKICAgICAgICBpZiBzZWxmLnRva2VuaXplcl9jbGFzczoKICAgICAgICAgICAgdG9rZW5pemVyX29iamVjdCA9IGdldGF0dHIoCiAgICAgICAgICAgICAgICBpbXBvcnRfbW9kdWxlKFBBQ0tBR0VfTU9EVUxFKSwgc2VsZi50b2tlbml6ZXJfY2xhc3MKICAgICAgICAgICAgKQogICAgICAgICAgICBzZWxmLnRva2VuaXplciA9IHRva2VuaXplcl9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYudG9rZW5pemVyX25hbWUpCiAgICAgICAgc2VsZi5waXBlID0gcGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9c2VsZi50YXNrLAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsIG9yIHNlbGYubW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPXNlbGYudG9rZW5pemVyLAogICAgICAgICAgICBmcmFtZXdvcms9c2VsZi5mcmFtZXdvcmssCiAgICAgICAgKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIGJvZHk6IGRpY3QpIC0+IExpc3Q6CiAgICAgICAgIiIiR2VuZXJhdGUgbW9kZWwgcHJlZGljdGlvbnMgZnJvbSBzYW1wbGUuIiIiCiAgICAgICAgaWYgc2VsZi5waXBlIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSB1c2UgYC5sb2FkKClgIikKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoYm9keVsiaW5wdXRzIl1bMF0sIGRpY3QpOgogICAgICAgICAgICAgICAgcmVzdWx0ID0gW3NlbGYucGlwZSgqKl9pbnB1dCkgZm9yIF9pbnB1dCBpbiBib2R5WyJpbnB1dHMiXV0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHNlbGYucGlwZShib2R5WyJpbnB1dHMiXSkKICAgICAgICAgICAgIyByZXBsYWNlIGxpc3Qgb2YgbGlzdHMgb2YgZGljdHMgaW50byBhIGxpc3Qgb2YgZGljdHM6CiAgICAgICAgICAgIGlmIGFsbChpc2luc3RhbmNlKHJlcywgbGlzdCkgZm9yIHJlcyBpbiByZXN1bHQpOgogICAgICAgICAgICAgICAgbmV3X3Jlc3VsdCA9IFtyZXNbMF0gZm9yIHJlcyBpbiByZXN1bHRdCiAgICAgICAgICAgICAgICByZXN1bHQgPSBuZXdfcmVzdWx0CgogICAgICAgICAgICBub25fc2VyaWFsaXphYmxlX3R5cGVzID0gW10KICAgICAgICAgICAgZm9yIHJlcyBpbiByZXN1bHQ6CiAgICAgICAgICAgICAgICBmb3Iga2V5LCB2YWwgaW4gcmVzLml0ZW1zKCk6CiAgICAgICAgICAgICAgICAgICAgaWYgdHlwZSh2YWwpIG5vdCBpbiBTRVJJQUxJWkFCTEVfVFlQRVM6CiAgICAgICAgICAgICAgICAgICAgICAgIG5vbl9zZXJpYWxpemFibGVfdHlwZXMuYXBwZW5kKHN0cih0eXBlKHZhbCkpKQogICAgICAgICAgICAgICAgICAgICAgICByZXNba2V5XSA9IHN0cih2YWwpCiAgICAgICAgICAgIGlmIG5vbl9zZXJpYWxpemFibGVfdHlwZXM6CiAgICAgICAgICAgICAgICBzZWxmLmNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJOb24tc2VyaWFsaXphYmxlIHR5cGVzOiB7bm9uX3NlcmlhbGl6YWJsZV90eXBlc30gd2VyZSBjYXN0ZWQgdG8gc3RyaW5ncyIKICAgICAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbigiRmFpbGVkIHRvIHByZWRpY3QgJXMiICUgZSkKICAgICAgICByZXR1cm4gcmVzdWx0Cgpmcm9tIG1scnVuLnJ1bnRpbWVzIGltcG9ydCBudWNsaW9faW5pdF9ob29rCmRlZiBpbml0X2NvbnRleHQoY29udGV4dCk6CiAgICBudWNsaW9faW5pdF9ob29rKGNvbnRleHQsIGdsb2JhbHMoKSwgJ3NlcnZpbmdfdjInKQoKZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuIGNvbnRleHQubWxydW5faGFuZGxlcihjb250ZXh0LCBldmVudCkK - commands: [] code_origin: '' origin_filename: '' requirements: - transformers==4.21.3 - tensorflow==2.9.2 - description: Generic Hugging Face model server. - default_handler: '' + function_kind: serving_v2 + default_class: HuggingFaceModelServer + base_image_pull: false + max_replicas: 4 + command: '' disable_auto_mount: false - clone_target_dir: '' + function_handler: hugging-face-serving-nuclio:handler + description: Generic Hugging Face model server. env: - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK value: enabled - priority_class_name: '' - preemption_mode: prevent - min_replicas: 1 - max_replicas: 4 - source: '' - function_kind: serving_v2 - function_handler: hugging_face_serving:handler - base_image_pull: false - default_class: HuggingFaceModelServer - secret_sources: [] - affinity: null - tolerations: null - security_context: {} verbose: false +kind: serving diff --git a/functions/master/hugging_face_serving/latest/src/item.yaml b/functions/master/hugging_face_serving/latest/src/item.yaml index d1f78769..48b063e4 100644 --- a/functions/master/hugging_face_serving/latest/src/item.yaml +++ b/functions/master/hugging_face_serving/latest/src/item.yaml @@ -1,9 +1,7 @@ apiVersion: v1 categories: -- huggingface - genai - model-serving -- machine-learning description: Generic Hugging Face model server. doc: '' example: hugging_face_serving.ipynb diff --git a/functions/master/hugging_face_serving/latest/static/documentation.html b/functions/master/hugging_face_serving/latest/static/documentation.html index a95b606c..65c7aeb8 100644 --- a/functions/master/hugging_face_serving/latest/static/documentation.html +++ b/functions/master/hugging_face_serving/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/latest/static/example.html b/functions/master/hugging_face_serving/latest/static/example.html index bc408d54..20ac581a 100644 --- a/functions/master/hugging_face_serving/latest/static/example.html +++ b/functions/master/hugging_face_serving/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/latest/static/function.html b/functions/master/hugging_face_serving/latest/static/function.html index ef13ff66..c30517c4 100644 --- a/functions/master/hugging_face_serving/latest/static/function.html +++ b/functions/master/hugging_face_serving/latest/static/function.html @@ -28,52 +28,37 @@
         
-kind: serving
 metadata:
   name: hugging-face-serving
-  tag: ''
-  hash: 1a489a57da861f129eb26e933f34e58927e41195
-  project: ''
-  labels:
-    author: yonish
   categories:
-  - huggingface
   - genai
   - model-serving
-  - machine-learning
+  tag: ''
 spec:
-  command: ''
-  args: []
+  default_handler: ''
+  min_replicas: 1
+  source: ''
   image: mlrun/ml-models
   build:
     functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmZyb20gYWJjIGltcG9ydCBBQkMKZnJvbSBpbXBvcnRsaWIgaW1wb3J0IGltcG9ydF9tb2R1bGUKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QKCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBwaXBlbGluZQoKaW1wb3J0IG1scnVuLnNlcnZpbmcKClBBQ0tBR0VfTU9EVUxFID0gInRyYW5zZm9ybWVycyIKU0VSSUFMSVpBQkxFX1RZUEVTID0gW2RpY3QsIGxpc3QsIHR1cGxlLCBzdHIsIGludCwgZmxvYXRdCgoKY2xhc3MgSHVnZ2luZ0ZhY2VNb2RlbFNlcnZlcihtbHJ1bi5zZXJ2aW5nLlYyTW9kZWxTZXJ2ZXIsIEFCQyk6CiAgICAiIiIKICAgIEh1Z2dpbmcgRmFjZSBNb2RlbCBzZXJ2aW5nIGNsYXNzLCBpbmhlcml0aW5nIHRoZSBWMk1vZGVsU2VydmVyIGNsYXNzIGZvciBiZWluZyBpbml0aWFsaXplZCBhdXRvbWF0aWNhbGx5IGJ5IHRoZQogICAgbW9kZWwgc2VydmVyIGFuZCBiZSBhYmxlIHRvIHJ1biBsb2NhbGx5IGFzIHBhcnQgb2YgYSBudWNsaW8gc2VydmVybGVzcyBmdW5jdGlvbiwgb3IgYXMgcGFydCBvZiBhIHJlYWwtdGltZSBwaXBlbGluZS4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgICAgIG5hbWU6IHN0ciwKICAgICAgICB0YXNrOiBzdHIsCiAgICAgICAgbW9kZWxfcGF0aDogc3RyID0gTm9uZSwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG1vZGVsX2NsYXNzOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIHRva2VuaXplcl9jbGFzczogc3RyID0gTm9uZSwKICAgICAgICBmcmFtZXdvcms6IHN0ciA9IE5vbmUsCiAgICAgICAgKipjbGFzc19hcmdzLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIGEgc2VydmluZyBjbGFzcyBmb3IgYSBIdWdnaW5nIGZhY2UgbW9kZWwuCgogICAgICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgIFRoZSBtbHJ1biBjb250ZXh0IHRvIHdvcmsgd2l0aAogICAgICAgIDpwYXJhbSBuYW1lOiAgICAgICAgICAgIFRoZSBuYW1lIG9mIHRoaXMgc2VydmVyIHRvIGJlIGluaXRpYWxpemVkCiAgICAgICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgTm90IGluIHVzZS4gV2hlbiBhZGRpbmcgYSBtb2RlbCBwYXNzIGFueSBzdHJpbmcgdmFsdWUKICAgICAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICBUaGUgbW9kZWwncyBuYW1lIGluIHRoZSBIdWdnaW5nIEZhY2UgaHViCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYG5scHRvd24vYmVydC1iYXNlLW11bHRpbGluZ3VhbC11bmNhc2VkLXNlbnRpbWVudGAKICAgICAgICA6cGFyYW0gbW9kZWxfY2xhc3M6ICAgICBUaGUgbW9kZWwncyBjbGFzcyB0eXBlIG9iamVjdCB3aGljaCBjYW4gYmUgcGFzc2VkIGFzIHRoZSBjbGFzcydzIG5hbWUgKHN0cmluZykuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTXVzdCBiZSBwcm92aWRlZCBhbmQgdG8gYmUgbWF0Y2hlZCB3aXRoIGBtb2RlbF9uYW1lYC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlLmcuLCBgQXV0b01vZGVsRm9yU2VxdWVuY2VDbGFzc2lmaWNhdGlvbmAKICAgICAgICA6cGFyYW0gdG9rZW5pemVyX25hbWU6ICBUaGUgdG9rZW5pemVyJ3MgbmFtZSBpbiB0aGUgSHVnZ2luZyBGYWNlIGh1YgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGUuZy4sIGBubHB0b3duL2JlcnQtYmFzZS1tdWx0aWxpbmd1YWwtdW5jYXNlZC1zZW50aW1lbnRgCiAgICAgICAgOnBhcmFtIHRva2VuaXplcl9jbGFzczogVGhlIG1vZGVsJ3MgY2xhc3MgdHlwZSBvYmplY3Qgd2hpY2ggY2FuIGJlIHBhc3NlZCBhcyB0aGUgY2xhc3MncyBuYW1lIChzdHJpbmcpLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgYmUgcHJvdmlkZWQgYW5kIHRvIGJlIG1hdGNoZWQgd2l0aCBgbW9kZWxfbmFtZWAuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5nLiwgYEF1dG9Ub2tlbml6ZXJgCiAgICAgICAgOnBhcmFtIGZyYW1ld29yazogICAgICAgVGhlIGZyYW1ld29yayB0byB1c2UsIGVpdGhlciBgInB0ImAgZm9yIFB5VG9yY2ggb3IgYCJ0ZiJgIGZvciBUZW5zb3JGbG93LiBUaGUgc3BlY2lmaWVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWV3b3JrIG11c3QgYmUgaW5zdGFsbGVkLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIG5vIGZyYW1ld29yayBpcyBzcGVjaWZpZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUgb25lIGN1cnJlbnRseSBpbnN0YWxsZWQuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgbm8gZnJhbWV3b3JrIGlzIHNwZWNpZmllZCBhbmQgYm90aCBmcmFtZXdvcmtzIGFyZSBpbnN0YWxsZWQsIHdpbGwgZGVmYXVsdCB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZXdvcmsgb2YgdGhlIGBtb2RlbGAsIG9yIHRvIFB5VG9yY2ggaWYgbm8gbW9kZWwgaXMgcHJvdmlkZWQuCiAgICAgICAgOnBhcmFtIGNsYXNzX2FyZ3M6ICAgICAgLQogICAgICAgICIiIgogICAgICAgIHN1cGVyKEh1Z2dpbmdGYWNlTW9kZWxTZXJ2ZXIsIHNlbGYpLl9faW5pdF9fKAogICAgICAgICAgICBjb250ZXh0PWNvbnRleHQsCiAgICAgICAgICAgIG5hbWU9bmFtZSwKICAgICAgICAgICAgbW9kZWxfcGF0aD1tb2RlbF9wYXRoLAogICAgICAgICAgICAqKmNsYXNzX2FyZ3MsCiAgICAgICAgKQogICAgICAgIHNlbGYudGFzayA9IHRhc2sKICAgICAgICBzZWxmLm1vZGVsID0gTm9uZQogICAgICAgIHNlbGYudG9rZW5pemVyID0gTm9uZQogICAgICAgIHNlbGYubW9kZWxfbmFtZSA9IG1vZGVsX25hbWUKICAgICAgICBzZWxmLnRva2VuaXplcl9uYW1lID0gdG9rZW5pemVyX25hbWUKICAgICAgICBzZWxmLm1vZGVsX2NsYXNzID0gbW9kZWxfY2xhc3MKICAgICAgICBzZWxmLnRva2VuaXplcl9jbGFzcyA9IHRva2VuaXplcl9jbGFzcwogICAgICAgIHNlbGYuZnJhbWV3b3JrID0gZnJhbWV3b3JrCiAgICAgICAgc2VsZi5waXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYpOgogICAgICAgICIiImxvYWQgYW5kIGluaXRpYWxpemUgdGhlIG1vZGVsIGFuZC9vciBvdGhlciBlbGVtZW50cyIiIgogICAgICAgIGlmIHNlbGYubW9kZWxfY2xhc3M6CiAgICAgICAgICAgIG1vZGVsX29iamVjdCA9IGdldGF0dHIoaW1wb3J0X21vZHVsZShQQUNLQUdFX01PRFVMRSksIHNlbGYubW9kZWxfY2xhc3MpCiAgICAgICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbF9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYubW9kZWxfbmFtZSkKICAgICAgICBpZiBzZWxmLnRva2VuaXplcl9jbGFzczoKICAgICAgICAgICAgdG9rZW5pemVyX29iamVjdCA9IGdldGF0dHIoCiAgICAgICAgICAgICAgICBpbXBvcnRfbW9kdWxlKFBBQ0tBR0VfTU9EVUxFKSwgc2VsZi50b2tlbml6ZXJfY2xhc3MKICAgICAgICAgICAgKQogICAgICAgICAgICBzZWxmLnRva2VuaXplciA9IHRva2VuaXplcl9vYmplY3QuZnJvbV9wcmV0cmFpbmVkKHNlbGYudG9rZW5pemVyX25hbWUpCiAgICAgICAgc2VsZi5waXBlID0gcGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9c2VsZi50YXNrLAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsIG9yIHNlbGYubW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPXNlbGYudG9rZW5pemVyLAogICAgICAgICAgICBmcmFtZXdvcms9c2VsZi5mcmFtZXdvcmssCiAgICAgICAgKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIGJvZHk6IGRpY3QpIC0+IExpc3Q6CiAgICAgICAgIiIiR2VuZXJhdGUgbW9kZWwgcHJlZGljdGlvbnMgZnJvbSBzYW1wbGUuIiIiCiAgICAgICAgaWYgc2VsZi5waXBlIGlzIE5vbmU6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSB1c2UgYC5sb2FkKClgIikKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoYm9keVsiaW5wdXRzIl1bMF0sIGRpY3QpOgogICAgICAgICAgICAgICAgcmVzdWx0ID0gW3NlbGYucGlwZSgqKl9pbnB1dCkgZm9yIF9pbnB1dCBpbiBib2R5WyJpbnB1dHMiXV0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHNlbGYucGlwZShib2R5WyJpbnB1dHMiXSkKICAgICAgICAgICAgIyByZXBsYWNlIGxpc3Qgb2YgbGlzdHMgb2YgZGljdHMgaW50byBhIGxpc3Qgb2YgZGljdHM6CiAgICAgICAgICAgIGlmIGFsbChpc2luc3RhbmNlKHJlcywgbGlzdCkgZm9yIHJlcyBpbiByZXN1bHQpOgogICAgICAgICAgICAgICAgbmV3X3Jlc3VsdCA9IFtyZXNbMF0gZm9yIHJlcyBpbiByZXN1bHRdCiAgICAgICAgICAgICAgICByZXN1bHQgPSBuZXdfcmVzdWx0CgogICAgICAgICAgICBub25fc2VyaWFsaXphYmxlX3R5cGVzID0gW10KICAgICAgICAgICAgZm9yIHJlcyBpbiByZXN1bHQ6CiAgICAgICAgICAgICAgICBmb3Iga2V5LCB2YWwgaW4gcmVzLml0ZW1zKCk6CiAgICAgICAgICAgICAgICAgICAgaWYgdHlwZSh2YWwpIG5vdCBpbiBTRVJJQUxJWkFCTEVfVFlQRVM6CiAgICAgICAgICAgICAgICAgICAgICAgIG5vbl9zZXJpYWxpemFibGVfdHlwZXMuYXBwZW5kKHN0cih0eXBlKHZhbCkpKQogICAgICAgICAgICAgICAgICAgICAgICByZXNba2V5XSA9IHN0cih2YWwpCiAgICAgICAgICAgIGlmIG5vbl9zZXJpYWxpemFibGVfdHlwZXM6CiAgICAgICAgICAgICAgICBzZWxmLmNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJOb24tc2VyaWFsaXphYmxlIHR5cGVzOiB7bm9uX3NlcmlhbGl6YWJsZV90eXBlc30gd2VyZSBjYXN0ZWQgdG8gc3RyaW5ncyIKICAgICAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbigiRmFpbGVkIHRvIHByZWRpY3QgJXMiICUgZSkKICAgICAgICByZXR1cm4gcmVzdWx0Cgpmcm9tIG1scnVuLnJ1bnRpbWVzIGltcG9ydCBudWNsaW9faW5pdF9ob29rCmRlZiBpbml0X2NvbnRleHQoY29udGV4dCk6CiAgICBudWNsaW9faW5pdF9ob29rKGNvbnRleHQsIGdsb2JhbHMoKSwgJ3NlcnZpbmdfdjInKQoKZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuIGNvbnRleHQubWxydW5faGFuZGxlcihjb250ZXh0LCBldmVudCkK
-    commands: []
     code_origin: ''
     origin_filename: ''
     requirements:
     - transformers==4.21.3
     - tensorflow==2.9.2
-  description: Generic Hugging Face model server.
-  default_handler: ''
+  function_kind: serving_v2
+  default_class: HuggingFaceModelServer
+  base_image_pull: false
+  max_replicas: 4
+  command: ''
   disable_auto_mount: false
-  clone_target_dir: ''
+  function_handler: hugging-face-serving-nuclio:handler
+  description: Generic Hugging Face model server.
   env:
   - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK
     value: enabled
-  priority_class_name: ''
-  preemption_mode: prevent
-  min_replicas: 1
-  max_replicas: 4
-  source: ''
-  function_kind: serving_v2
-  function_handler: hugging_face_serving:handler
-  base_image_pull: false
-  default_class: HuggingFaceModelServer
-  secret_sources: []
-  affinity: null
-  tolerations: null
-  security_context: {}
 verbose: false
+kind: serving
 
         
     
diff --git a/functions/master/hugging_face_serving/latest/static/hugging_face_serving.html b/functions/master/hugging_face_serving/latest/static/hugging_face_serving.html index b07151ed..848cc67e 100644 --- a/functions/master/hugging_face_serving/latest/static/hugging_face_serving.html +++ b/functions/master/hugging_face_serving/latest/static/hugging_face_serving.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/hugging_face_serving/latest/static/item.html b/functions/master/hugging_face_serving/latest/static/item.html index 62cc1b5b..784df913 100644 --- a/functions/master/hugging_face_serving/latest/static/item.html +++ b/functions/master/hugging_face_serving/latest/static/item.html @@ -30,10 +30,8 @@ apiVersion: v1 categories: -- huggingface - genai - model-serving -- machine-learning description: Generic Hugging Face model server. doc: '' example: hugging_face_serving.ipynb diff --git a/functions/master/load_dataset/1.2.0/static/documentation.html b/functions/master/load_dataset/1.2.0/static/documentation.html index ef5de639..06068936 100644 --- a/functions/master/load_dataset/1.2.0/static/documentation.html +++ b/functions/master/load_dataset/1.2.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/load_dataset/1.2.0/static/example.html b/functions/master/load_dataset/1.2.0/static/example.html index ffb7b6c8..63619eb0 100644 --- a/functions/master/load_dataset/1.2.0/static/example.html +++ b/functions/master/load_dataset/1.2.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/load_dataset/1.2.0/static/load_dataset.html b/functions/master/load_dataset/1.2.0/static/load_dataset.html index 5fe817e8..9bd07517 100644 --- a/functions/master/load_dataset/1.2.0/static/load_dataset.html +++ b/functions/master/load_dataset/1.2.0/static/load_dataset.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/load_dataset/latest/static/documentation.html b/functions/master/load_dataset/latest/static/documentation.html index ef5de639..06068936 100644 --- a/functions/master/load_dataset/latest/static/documentation.html +++ b/functions/master/load_dataset/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/load_dataset/latest/static/example.html b/functions/master/load_dataset/latest/static/example.html index ffb7b6c8..63619eb0 100644 --- a/functions/master/load_dataset/latest/static/example.html +++ b/functions/master/load_dataset/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/load_dataset/latest/static/load_dataset.html b/functions/master/load_dataset/latest/static/load_dataset.html index 5fe817e8..9bd07517 100644 --- a/functions/master/load_dataset/latest/static/load_dataset.html +++ b/functions/master/load_dataset/latest/static/load_dataset.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/mlflow_utils/1.1.0/src/function.yaml b/functions/master/mlflow_utils/1.1.0/src/function.yaml new file mode 100644 index 00000000..623f054f --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/function.yaml @@ -0,0 +1,32 @@ +verbose: false +spec: + command: '' + source: '' + default_class: MLFlowModelServer + function_kind: serving_v2 + build: + functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhIG1vZGVsIHRoYXQgd2FzIGxvZ2dlZCBieSB0aGUgTUxGbG93IHRyYWNrZXIgbW9kZWwKICAgICAgICAiIiIKICAgICAgICAjIFVuemlwIHRoZSBtb2RlbCBkaXIgYW5kIHRoZW4gdXNlIG1sZmxvdydzIGxvYWQgZnVuY3Rpb24KICAgICAgICBtb2RlbF9maWxlLCBfID0gc2VsZi5nZXRfbW9kZWwoIi56aXAiKQogICAgICAgIG1vZGVsX3BhdGhfdW56aXAgPSBtb2RlbF9maWxlLnJlcGxhY2UoIi56aXAiLCAiIikKCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUobW9kZWxfZmlsZSwgInIiKSBhcyB6aXBfcmVmOgogICAgICAgICAgICB6aXBfcmVmLmV4dHJhY3RhbGwobW9kZWxfcGF0aF91bnppcCkKCiAgICAgICAgc2VsZi5tb2RlbCA9IG1sZmxvdy5weWZ1bmMubG9hZF9tb2RlbChtb2RlbF9wYXRoX3VuemlwKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIHJlcXVlc3Q6IERpY3Rbc3RyLCBBbnldKSAtPiBsaXN0OgogICAgICAgICIiIgogICAgICAgIEluZmVyIHRoZSBpbnB1dHMgdGhyb3VnaCB0aGUgbW9kZWwuIFRoZSBpbmZlcnJlZCBkYXRhIHdpbGwKICAgICAgICBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleSBvZiB0aGUgcmVxdWVzdC4KCiAgICAgICAgOnBhcmFtIHJlcXVlc3Q6IFRoZSByZXF1ZXN0IHRvIHRoZSBtb2RlbCB1c2luZyB4Z2Jvb3N0J3MgcHJlZGljdC4KICAgICAgICAgICAgICAgIFRoZSBpbnB1dCB0byB0aGUgbW9kZWwgd2lsbCBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleS4KCiAgICAgICAgOnJldHVybjogVGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBvbiB0aGUgZ2l2ZW4gaW5wdXQuCiAgICAgICAgIiIiCgogICAgICAgICMgR2V0IHRoZSBpbnB1dHMgYW5kIHNldCB0byBhY2NlcHRlZCB0eXBlOgogICAgICAgIGlucHV0cyA9IHBkLkRhdGFGcmFtZShyZXF1ZXN0WyJpbnB1dHMiXSkKCiAgICAgICAgIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCdzIHByZWRpY3QgZnVuY3Rpb246CiAgICAgICAgcHJlZGljdGlvbnMgPSBzZWxmLm1vZGVsLnByZWRpY3QoaW5wdXRzKQoKICAgICAgICAjIFJldHVybiBhcyBsaXN0OgogICAgICAgIHJldHVybiBwcmVkaWN0aW9ucy50b2xpc3QoKQoKZnJvbSBtbHJ1bi5ydW50aW1lcyBpbXBvcnQgbnVjbGlvX2luaXRfaG9vawpkZWYgaW5pdF9jb250ZXh0KGNvbnRleHQpOgogICAgbnVjbGlvX2luaXRfaG9vayhjb250ZXh0LCBnbG9iYWxzKCksICdzZXJ2aW5nX3YyJykKCmRlZiBoYW5kbGVyKGNvbnRleHQsIGV2ZW50KToKICAgIHJldHVybiBjb250ZXh0Lm1scnVuX2hhbmRsZXIoY29udGV4dCwgZXZlbnQpCg== + requirements: + - mlflow==2.12.2 + - lightgbm + - xgboost + code_origin: '' + origin_filename: '' + image: mlrun/mlrun + base_image_pull: false + default_handler: '' + max_replicas: 4 + disable_auto_mount: false + min_replicas: 1 + description: Mlflow model server, and additional utils. + function_handler: mlflow-utils-nuclio:handler + env: + - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK + value: enabled +metadata: + categories: + - model-serving + - utils + name: mlflow-utils + tag: '' +kind: serving diff --git a/functions/master/mlflow_utils/1.1.0/src/item.yaml b/functions/master/mlflow_utils/1.1.0/src/item.yaml new file mode 100644 index 00000000..27e61ab4 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/item.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +categories: +- model-serving +- utils +description: Mlflow model server, and additional utils. +doc: '' +example: mlflow_utils.ipynb +generationDate: 2024-05-23:12-00 +hidden: false +icon: '' +labels: + author: zeevr +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.8.0 +name: mlflow_utils +platformVersion: '' +spec: + customFields: + default_class: MLFlowModelServer + filename: mlflow_utils.py + handler: handler + image: mlrun/mlrun + kind: serving + requirements: + - mlflow==2.12.2 + - lightgbm + - xgboost +url: '' +version: 1.1.0 diff --git a/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.ipynb b/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.ipynb new file mode 100644 index 00000000..165dafc6 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.ipynb @@ -0,0 +1,1353 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c478ebb2", + "metadata": {}, + "source": [ + "# MLflow tracker demo\n", + "\n", + "This demo demonstrates how to seamlessly integrate and transfer logs from MLflow to MLRun,
\n", + "creating a unified and powerful platform for your machine learning experiments.\n", + "\n", + "You can combine MLflow and MLRun for a comprehensive solution for managing, tracking, and deploying machine learning models. \n", + "\n", + "This notebook guides you through the process of:\n", + "\n", + "1. Setting up the integration between MLflow and MLRun.\n", + "2. Extracting data, metrics, and artifacts from MLflow experiments.\n", + "3. Creating MLRun artifacts and projects to organize and manage the transferred data.\n", + "4. Leveraging MLRun's capabilities for model deployment and data processing.\n", + "\n", + "By the end of this demo, you will have a understanding of how to establish a smooth flow of data between MLflow and MLRun.\n", + "\n", + "## MLRun installation and configuration\n", + "Before running this notebook make sure the mlrun package is installed (pip install mlrun) and that you have configured the access to MLRun service." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ab49e1f1", + "metadata": {}, + "outputs": [], + "source": [ + "# Install MLRun and scikit-learn if not already installed. Run this only once. Restart the notebook after the install!\n", + "# %pip install mlrun scikit-learn~=1.3.0" + ] + }, + { + "cell_type": "markdown", + "id": "1770566a", + "metadata": {}, + "source": [ + "Then you can import the necessary packages." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d2dfd8b-65c4-417b-b66e-99f44b015ee7", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import os\n", + "import mlrun\n", + "from mlrun.datastore.targets import ParquetTarget\n", + "import mlrun.feature_store as fstore" + ] + }, + { + "cell_type": "markdown", + "id": "7c4513d4", + "metadata": {}, + "source": [ + "Create a project for this demo:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "43ea863f-02d5-45f2-8143-306ce3bb6c58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:34:40,940 [info] Project loaded successfully: {'project_name': 'mlflow-tracking-example-guy'}\n" + ] + } + ], + "source": [ + "# Create a project for this demo:\n", + "project = mlrun.get_or_create_project(name=\"mlflow-tracking-example\", context=\"./\")" + ] + }, + { + "cell_type": "markdown", + "id": "94413ee8", + "metadata": {}, + "source": [ + "Set all the necessary environment variables for the Databricks cluster:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "22f94f89-acce-442d-93ff-b2d08d3a35a4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "DATABRICKS_HOST=\"add your host\"\n", + "DATABRICKS_TOKEN=\"add your token\"\n", + "DATABRICKS_CLUSTER_ID=\"add your cluster id\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7af310da-fd02-444e-8619-43ba6dcdb0a4", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"DATABRICKS_HOST\"] = DATABRICKS_HOST\n", + "os.environ[\"DATABRICKS_TOKEN\"] = DATABRICKS_TOKEN\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d98e823c-3a27-4532-9a2d-6398ea4e1778", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the Databricks environment variables\n", + "job_env = {\n", + " \"DATABRICKS_HOST\": DATABRICKS_HOST,\n", + " \"DATABRICKS_CLUSTER_ID\": DATABRICKS_CLUSTER_ID\n", + "}\n", + "secrets = {\"DATABRICKS_TOKEN\": DATABRICKS_TOKEN}\n", + "\n", + "# Set the secrets in the project\n", + "project.set_secrets(secrets)" + ] + }, + { + "cell_type": "markdown", + "id": "37d75366", + "metadata": {}, + "source": [ + "## Create a feature set and ingest data\n", + "\n", + "This is a short example of how to create a feature set about music preferences." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5701c04a-8442-4958-8f4c-265bf4c9b06a", + "metadata": {}, + "outputs": [], + "source": [ + "# create df\n", + "columns = [\"id\", \"name\", \"age\", \"gender\", \"favorite_music_type\"]\n", + "data = [\n", + " (1, \"Alice\", 20, \"f\", \"Pop\"),\n", + " (2, \"Bob\", 30, \"m\", \"Rock\"),\n", + " (3, \"Charlie\", 25, \"m\", \"Pop\"),\n", + " (4, \"David\", 40, \"m\", \"Classical\"),\n", + " (5, \"Eva\", 18, \"f\", \"Pop\"),\n", + " (6, \"Frank\", 32, \"m\", \"Rock\"),\n", + " (7, \"Grace\", 28, \"f\", \"Pop\"),\n", + " (8, \"Henry\", 45, \"m\", \"Classical\"),\n", + " (9, \"Ivy\", 22, \"f\", \"Pop\"),\n", + " (10, \"Jack\", 38, \"m\", \"Classical\"),\n", + " (11, \"Karen\", 27, \"f\", \"Pop\"),\n", + " (12, \"Liam\", 19, \"m\", \"Pop\"),\n", + " (13, \"Mia\", 27, \"f\", \"Rock\"),\n", + " (14, \"Nora\", 31, \"f\", \"Rock\"),\n", + " (15, \"Oliver\", 29, \"m\", \"Pop\"),\n", + " (16, \"Ben\", 38, \"m\", \"Pop\"),\n", + " (17, \"Alicia\", 20, \"f\", \"Pop\"),\n", + " (18, \"Bobby\", 30, \"m\", \"Rock\"),\n", + " (19, \"Charlien\", 22, \"f\", \"Pop\"),\n", + " (20, \"Davide\", 40, \"m\", \"Classical\"),\n", + " (21, \"Evans\", 19, \"m\", \"Pop\"),\n", + " (22, \"Franklin\", 34, \"m\", \"Rock\"),\n", + " (23, \"Grace\", 22, \"f\", \"Pop\"),\n", + " (24, \"Henrik\", 48, \"m\", \"Classical\"),\n", + " (25, \"eevee\", 29, \"f\", \"Pop\"),\n", + " (26, \"Jack\", 75, \"m\", \"Classical\"),\n", + " (27, \"Karen\", 26, \"f\", \"Pop\"),\n", + " (28, \"Lian\", 21, \"f\", \"Pop\"),\n", + " (29, \"kia\", 27, \"f\", \"Rock\"),\n", + " (30, \"Novak\", 30, \"m\", \"Rock\"),\n", + " (31, \"Olivia\", 29, \"f\", \"Pop\"),\n", + " (32, \"Benjamin\", 18, \"m\", \"Pop\")\n", + "]\n", + "df = pd.DataFrame(data, columns=columns)" + ] + }, + { + "cell_type": "markdown", + "id": "4b91576b", + "metadata": {}, + "source": [ + "Transfer the data to DataBricks." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8679b0bb-0da6-4c35-9345-6cf0e83e19b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'dbfs:///demos/mlrun_databricks_demo/1711553684480_33/music.parquet'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Where to save the data in DataBricks\n", + "target_path = f\"dbfs:///demos/mlrun_databricks_demo/music.parquet\"\n", + "output_path = f\"dbfs:///demos/mlrun_databricks_demo/music_output_new.parquet\"\n", + "\n", + "targets = [ParquetTarget(path=target_path)]\n", + "\n", + "# Create a feature set and ingest the data\n", + "fset = fstore.FeatureSet(name=\"music_fset\", entities=[fstore.Entity(\"name\")])\n", + "fstore.ingest(fset, df, targets=targets, overwrite=True)\n", + "\n", + "# Get the target path and check it\n", + "dbfs_data_path = fset.get_target_path()\n", + "dbfs_data_path" + ] + }, + { + "cell_type": "markdown", + "id": "fe173be8-18eb-40ec-9662-6639b0deaedb", + "metadata": {}, + "source": [ + "We can look and see how how our data is logged in the DataBricks cluster:\n", + "(only top 20 rows)" + ] + }, + { + "attachments": { + "f7ad0425-26fe-482c-b97c-c9493b05fbf2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzcAAAJMCAYAAADHQ1hsAAABU2lDQ1BJQ0MgUHJvZmlsZQAAGJVtkE1LAlEUhh9LEcPIoFW0cJGtNMKEaFkSEoSJfVAtonH8CvwYxpHoH1Q/IILqL0QtomUuWrcpomWuCtpF2KJkOqOVWp3Ly3l473svhwNdKJqWswP5gqHHI9PeldU1r/NJLhy48OBS1JI2FYvNSYTv3lm1O2xWvwlYf81UT8LPgd1ib/U4en+efPib76ieZKqkSv8QjaiaboBtWDi2ZWgWixjQZSjhHYszTT6yONHk00ZmMR4WvhL2qFklKXwr7E+0+Zk2zufK6tcM1vTuVGFpwZpHNMQyEYJMMCV7+T8XauTCFNHYRmeTDFkMvPJGk5MjJTxLAZVR/MJBxkQha7+/99by9D2YTAs8trz1Q7goQ/9by/OdQZ8PKvOaois/27TV7KX0eLDJ7kFwVEzzxQTnBtSvTfP9wDTr+9D9CpfRT9xxYozukjtkAAAAVmVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADkoYABwAAABIAAABEoAIABAAAAAEAAAM3oAMABAAAAAEAAAJMAAAAAEFTQ0lJAAAAU2NyZWVuc2hvdBIyf/QAAAHWaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjU4ODwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj44MjM8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KyQQwEgAAQABJREFUeAHsnQeYFEXTgOvIOeecMwIiQRERBUVEFMWIip9ZUcAsBkRBEQQxoiBiBDEgKphRMiJIVHLOOedw3N9vH73M7W2649LyVz3P7s5Mx3mnZ6arq7o3Js6IqCgBJaAElIASUAJKQAkoASWgBKKcQKYor79WXwkoASWgBJSAElACSkAJKAElYAmocqMNQQkoASWgBJSAElACSkAJKIGzgoAqN2fFZdSTUAJKQAkoASWgBJSAElACSkCVG20DSkAJKAEloASUgBJQAkpACZwVBFS5OSsuo56EElACSkAJKAEloASUgBJQAqrcaBtQAkpACSgBJaAElIASUAJK4KwgoMrNWXEZ9SSUgBJQAkpACSgBJaAElIASUOVG24ASUAJKQAkoASWgBJSAElACZwUBVW7OisuoJ6EElIASUAJKQAkoASWgBJSAKjfaBpSAElACSkAJKAEloASUgBI4KwiocnNWXEY9CSWgBJSAElACSkAJKAEloARUudE2oASUgBJQAkpACSgBJaAElMBZQUCVm7PiMupJKAEloASUgBJQAkpACSgBJaDKjbYBJaAElIASUAJKQAkoASWgBM4KAqrcnBWXUU9CCSgBJaAElIASUAJKQAkoAVVutA0oASWgBJSAElACSkAJKAElcFYQUOXmrLiMehJKQAkoASWgBJSAElACSkAJZIkEQWxsrJw4cULi4uIiia5xlIASUAJKQAkoASWgBJSAElACaU4gIuUGxSYmJsZ+0ryGWqASUAJKQAkoASWgBJSAElACSiACAhG5panFJgKSGkUJKAEloASUgBJQAkpACSiBdCUQkXKTrjXUwpWAElACSkAJKAEloASUgBJQAhEQUOUmAkgaRQkoASWgBJSAElACSkAJKIGMT0CVmzO4RmvWrJHRo0fL77//HnEuP/30k3zyySf2s3LlypDpDh8+LF9++aXs3bs3ZLxIA0+ePCnuE2maSONt2LDBsog0fkrGiz18SLaOHS2H1sTz3D1jiuya/EfQImIPHZKNIz+S43v32DiH16+VTV99FjS+N+DYrh2y5buvbPqTx455g1Ju21wns3pH8PzChMedjA2aFkabvxkZNDy9A0LV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yhvWo0BhxMXawwICWJr8BXc00lJvFSWgBJSAElACSkAJKAElcDYRiNx8ko5nTSf+2muvjWjZaKrJEs7Mwwml2BAP6wruWV7FhuNewTITSLEhDspDSis25Mv5Yq0JprxwXsHCSE+dAyk2hDHXSRUbSKgoASWgBJSAElACSkAJnG0EosJyA3SsDf/995+13NB5DyZYNRYuXBg2XrD0HMeis2DBAqlWrZoqAqFAaZgSUAJKQAkoASWgBJSAEshABKJGuclAzLQqSkAJKAEloASUgBJQAkpACWRAAsFNIBmwslolJaAElIASUAJKQAkoASWgBJRAMAKq3AQjo8eVgBJQAkpACSgBJaAElIASiCoCqtxE1eXSyioBJaAElIASUAJKQAkoASUQjIAqN8HI6HEloASUgBJQAkpACSgBJaAEooqAKjdRdbm0skpACSgBJaAElIASUAJKQAkEI6DKTTAyelwJKAEloASUgBJQAkpACSiBqCKgyk1UXS6trBJQAkpACSgBJaAElIASUALBCKhyE4yMHlcCSkAJKAEloASUgBJQAkogqgiochNVl0srqwSUgBJQAkpACSgBJaAElEAwAqrcBCOjx5WAElACSkAJKAEloASUgBKIKgKq3ETV5dLKKgEloASUgBJQAkpACSgBJRCMgCo3wcjocSWgBJSAElACSkAJKAEloASiioAqN1F1ubSySkAJKAEloASUgBJQAkpACQQjkCVYQEY8vnPnTtm/f39EVcubN68ULlw4orgaSQkoASWgBJSAElACSkAJKIHoJxA1ys2CBQtk8ODBEhcXFxH1mJgYefDBB+Wcc86JKL5/pNjYWPnggw/kuuuuk6JFi9rgadOmydy5c6VRo0bSpEkT/yRRv79t2zb59ttv5d5775VMmVLPqDd06FBp166dlCpVKiyzWbNmyY4dO+SKK64IGzepEZYvXy4TJ06UAgUKyPXXX5/U5KkWf8+ePbJx40Zf/rQ/PrTp1JBjx44JLPylSJEiUrx4cf/DUbM/efJkWbRoka3vVVddJUePHpUffvhB7r//fsmRI0ei89iwYYP88ssvcvfddycK8z/w008/WTYNGzb0Dzrr96P13Pft2ycrV6607aB8+fJSsmTJqLhW3vcOz4FQbdh7QuGu03fffSeVKlUK+Y6cMWOGzJs3z2bbpk0bqVChgrcI3VYCSkAJ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BQsOuq9QoUIgUxhWGxJZRENw6yKNO8/xrbfeGiiS+KBw4tvilzzXZFrz9SHTGjE3wcK53mU1eH/wOi57KDekhEaRYTCGJAfE7nlXN87v2LGju4z3DIMl3bt3NzK5oeAgWIlJfCARAREQAREQARGITOCIw1mLshU6w0lM4Hng8NeeQ2m2Y3+abdp3yOqm7baqlSpEvMOLL77oJvHkH3Y0hZnGycpEZ6h48eJhiyY4GaVnxowZbgLOsCflYiejqAQr06khJXSkzg0ucn7CwFzcJqEvSeW2JTT4JK0cVlbiaFBkvPJAU6688krXqadjj1Uj9Lhvbl7fI6SExtqKu5d3JfVlZ7VkAk6ui4fyj0WHiUBRBokhCmZHvWP1jsuKiY6LgAiIgAiIQKISSErLDTDxY88qxWusoDP6HK97x6pNKlcEYkmAeWsym7uG5zlayUDCtQP3tNxO8BvLeoWra/A+Bk7ief/gumhdBERABERABJKBQFLG3CQDWNVRBEQgawKNGjXSQEHWmHSGCIiACIiACIhANgnE3C3NB8wGp07NZt3yfBoed7ijZJXNKM83ilAA96bd8XBniVClqO1O5bZFDZIKihqBeL5HotaIGBQU73dcDJqkIkVABERABEQgTwRi7pZGUC+feAj+6fFSbGhvbt1g4sEqp/dM5bbllIXOjz2BeL5HYt+63N8h3u+43NdcV4qACIiACIhAbAjILS02XFWqCIiACIiACIiACIiACIhAPhOQcpPPwHU7ERABERABERABERABERCB2BCQchMbripVBERABERABERABERABEQgnwlIucln4LqdCIiACIiACIiACIiACIhAbAjEXLn54osv7I033ohN7f9XKhPdhZuLlEk+J0yY4GYFz20FKDdc2ZTH7OKHDh2KWPRTTz1lmzdvjng8mQ+kctvi+bv8+uuvtn79emOSWi+bNm0yePNhMst4CRNGfvDBB1G9/SeffBJo20cffRSx7Px4j4TenOeed0usJTtcmQB12rRp9ssvv6SrTjTecekK1IYIiIAIiIAIJDmBmCs3q1evtnfeeSdmmKZPn26XXHKJTZ48OcM9+Mf/9ttvZ6qAZLgoaMdvv/1ml112mbVv3z6dgvTtt9/a5Zdfbj169LAOHTrYK6+8EnTVH6sodVu3bv1jRwqtpXLb4vEz/fDDD9azZ0/r1q2b3X777e7vjo4/cvDgQTdL/ccff2yvvvpqPKrn7rl48WLXwY5mBUgp/uOPP9qsWbNs/vz5EYuO9Xsk3I157nm3XHrppXbDDTfYzJkzIw50hLs+u/uyw3X37t1Osd25c2e6YvP6jktXmDZEQAREQAREIAUIxDwVdCwZrVu3zl5++eWY3eKJJ55Ip9RwI0ZyBwwYYLVq1bK+ffvas88+a1OmTLF69epZ3bp1Y1YXFZy6BJjD5c4777SyZcs6S2Pp0qXt008/dXMk0eqaNWva/fffb6+99po9//zzcQOBIt+qVauo3v+8884zPrQvEaVNmzauzV9++aU9/fTTbrDilltuiWpVY8E1qhVUYSIgAiIgAiKQRASSVrnBHWzIkCF27bXXGtabaMu8efOM0eKuXbumG63+/PPP3cSg119/vZUpU8a2bNnibv3Pf/4z6ZUbOtj79+83rAhnnnmmffXVV06Z69evn1Peos1Y5f2XwOzZs93fFMp05cqV3c6zzjor23iWLVtmEydOtO3bt7trULzvuuuuQFlYIMePH+8UJiwlKFGDBw+2qlWruvO/++47p6DTgUeqVatmo0ePdut8ffbZZ4FtruHaYMGqgPvn0qVL3d9PxYoVnbJCOVnVLbicRFw//vjj7c9//rP74BqG5QzrWokSJdzAyptvvulcxZist2nTpnbHHXdY4cKFXbvHjh1rY8aMsWLFirmm4X72yCOP2GOPPWbly5fPlCsucQycYPVmQIXnUSICIiACIiACIpA1gZi7pWVdhdydwT/+Y445xjp16pS7AjK56ueff7Ynn3zS7r77bjvuuOPSnemVmerVq9tbb71l27ZtM9ZRCJJdiA+iLV26dLEFCxZY586drUqVKhHd7pK9vYlS/++//95Zabxik9N6oejzHIwbN86GDRtm+/bts/79+weKmTp1qlM8HnroIee+icJ+5JF/PPrDhw93SgkdcTrkZ5xxRuBaVmrXru2slVgnN2zYkO4YnXCsLliaunfv7pSciy++2Pbs2ePOy6pu6QpL8I2GDRu6Gvo4uj/96U9266232qRJk5wVd8mSJa79nFS/fn3btWuXzZkzJ9AqXGS5BsUGyYwrAwszZsxwbq/8lsQcSURABERABERABLIm8EcPJ+tzE+YMRkCJ+eCfPjN0R1tGjBhhp512mhuJDS2bUXAEBYgA7wceeMBwIyIQPBWkcePGxgdp0aKFnXLKKRbq558K7UykNsA3VInOSf2aN29uF154ofsb3Lhxo51wwgnu79MHw/uEGLi/8bfKuSitXjjv999/d88S16LcBgt1q1Gjhrs2eD/rJD/gc/XVV1u7du1cubhZ0blHsqqbOylJvrwFhmcfIR6vUaNGLmkIiky5cuVszZo17hiWHKwt7777rtuGPQMG//d//+e2+cqMK3E4WI3g2qRJE7viiisC12lFBERABERABEQgMoGkVG4YaSa+hWxluL3QMcOigtKTV6EDsmrVKsMtiLKJ60GWL19uKDbFixd32yg1dFQYzWaU2u93B5P466ijjrKjjz7atYBloUKFXEB7Ejcp4auOe6O3dOSmslgHsNzg7sTfLNZEhEQEyJVXXml16tQx3AtRPLDuBCvjuLBxf2JJKOeZZ55x12Xny1ssGQwIJ1nVLdw1ibrPM0NBREaNGuXYEnOH6x5ua8FZ7rBg8V4i292iRYvce6p169bZah6WGqyoXrDySERABERABERABLImkJQxN7ijkYWMzgWCcoNbDCPUgwYNcvty+0XMCSOmBA8jdFgQ3NTuvfdeq1SpkttmJJYMSggj16noE49VLBaWMQdNXwECdFzff/995/J14oknBvaHrqBs8ncXKrijnXvuudanTx93iPgbb0FgBzE2Q4cOdcoOFgGUIO5D7AiCdQ7XKmJnsIiS/Q+LC7EmWQmKGcLAQrA1yF+XVd38eTzTuNMlsvjsdVi3sJCRFvuee+6xc845x1WbbHfBqeGxXmHtIRMcrocNGjRw75bstBH2ZMfzEuoO6PdrKQIiIAIiIAIikJ5AUlpu6Lwx54P/0OkjbWteFRvQMLLqy2VJHAHC6CydlVNPPdXFR5QsWdJ1xggoRiFq27atO09fIpBTAmQgK1KkiAs2p9NMJ5+0yCtWrEhXFO5JKPK4OuEa5a0EKC9scx2WGxSlYJl3ODkG5WKVI/MacR/BShKxY7hVYX3E/QwJ7qSzzvncGxc21v31nF+qVCmX1IP6cg5WTq9cZVU3X89mzZo5i+nKlSvTudT54/FawpVEC8T4wYnU0CgsPP8IVjKefwL/fTxecF2x7hJrgzUYt71gyYwryiWWaX47yiUNtUQEREAEREAERCBrAklpucm6WbE7g0BsArMJoiYwG2GZCmmg6fRK8p8ASgdWSJRzAtS9DBw40K+6JYHoxGDQ0cYi0qtXL9dhvvHGG41MaySAQHCpXLhwoVvnC4vD448/HthGQcdVzctLL73ksq2xzUABHXisOV5uuukm19H22x07dnSrWHvIjIZV6OGHH3ZJBzjA3xGuo0hWdXMnHf5q2bKlUwB4rlCQGMDIbYIFX2Zel7QDRXHu3LkungbLjFdQUHCuuuoql5r7ueeec8opLqo+Hsffm0EP+MIVBS5YMuMKf2Lf/O+GUioRAREQAREQARHImsARh125/pP1aWaclHb468Dhrz2H0mzH/jTbtO+Q1U3bbVUrVYhYBDOq43dPRyi/BVcZ5qIh6xBuL9EUsDFqi/89HZdwQkeIDiopYlNNUrlt8fytSNXsg9Mj/V1Fqh/xLwS1h1NSca9kwkfcKomjChVmvifuBoUit66IxKTh2lahQoUM98isbqF1Cbcdz/dIuPr4fVhfSAjhM6D5/dFa8rvwrvGWotByY/mOC72XtkVABERABEQgGQhk7OUkQ60ToI50ABm1lohANAngnsYnN5LZ3yOZufy8NuHKJs6MT14EawafcJJZ3cKdnyz7UBRjpdjAIK+/SbJwVD1FQAREQAREIFoEkjLmJlqNVzkiIAIiIAIiIAIiIAIiIAKpQyDmbmk+EJl5H/JbcOfAzado0aL5fWt3P+5Nu8O5CcWlQlG8aSq3LYqYVFSUCMTzPRKlJsSkmHi/42LSKBUqAiIgAiIgAnkgEHO3NIKl+cRDcB2Ll2JDe3PrXhQPVjm9Zyq3LacsdH7sCcTzPRL71uX+DvF+x+W+5rpSBERABERABGJDQG5pseGqUkVABERABERABERABERABPKZgJSbfAau24mACIiACIiACIiACIiACMSGgJSb2HBVqSIgAiIgAiIgAiIgAiIgAvlMQMpNPgPX7URABERABERABERABERABGJDIObKzRdffGFvvPFG1GuflpZmwR+yBoUKkxZOmDDBzXgeeiwa20xMyCR+keSpp56yzZs3Rzqc0Pv379/vZmefNm1a2DYkc9sSGnwSVs4/h+GewWg1J1bvkWjVL1w58PBs/DLajGL9jgvXLu0TAREQAREQgUQmEPNsaatXr7Y5c+ZYhw4dosZhxYoVNmDAgHTlNWjQwB555JF0+/jH//bbb1uPHj1ylI6ZNMdvvvmmzZgxw+jkv/rqqy6lsy989uzZ9txzzxmzh5PmuWXLlnb33XdnmNkdpY56ValSxV+aFMt9+/ZZly5djIkfmdG+YcOGGdqQrG1Lih8giSq5YMECGzFiRKDG/K1fcMEF7nmPZpbEWLxHApWO0QrvHd5BwcL7YubMmcG78rSe23dcnm6qi0VABERABEQggQnEXLmJZdsfe+wxK1y4sLtFNFMT33fffbZ9+3arWbOmrVq1yoJHW3fu3Gljxoyx1q1b28033+yODxo0yJo3b24tWrSIZXPzrexFixa5e2G1ScU5evINZAG4kX82hg0b5gYAFi9ebFOmTDGek169ehUAApk38dRTT7WbbropcNKRR8bcWB64l1ZEQAREQAREoCASSGrlhlFilJpodxjuuusuq1q1qr333ntOeQn+w9izZ4/bbNKkiVOssGqgAKxZsybplZulS5c6xY02/v77787iRWN79+5tp59+ejAGrYtAOgIVK1a0MmXK2EknnWRz5841rKsIk2/iGjp//ny3XqtWLevbt69xPtKnTx+rXLmy4Xa2e/duq1evnvXr189KlCjhjif7V/Hixa1atWoZmsHAwcqVK2348OGBY1hDsXIzeMLgCpbodevWuWexfPnyTlnkvSMRAREQAREQARGITCCplZuuXbu6ltE5opN08sknR25pDo6ceOKJEc/mWOPGje0f//iHbdq0yXVQUG7atm0b8ZpkOXDKKafY0KFD7Z///Kfhesc6UrZs2WRpguoZZwK4SfE5//zzXU2mT5/u/pauv/565+KIooNl9Omnn3ZunFu3brXvvvvObrzxRitdurSNHj3aXnjhhZSx+nz22WeGZdcLik737t0Ni86LL75oGzZsMP++wf3VKy8ohQwo3HHHHW7whGODBw+2qVOnWqlSpXxxWoqACIiACIiACIQQSErlpkKFCm70t0aNGvbVV18ZHSg6TMTBRNM9LYRVYBMlatmyZfbaa68Z8Tl05IoWLRo4nqwrxx57rJ1wwgmu80S8BOsSEcgOgYEDBzoLw5YtW6xYsWJ21VVXucvmzZtndevWtU6dOrlt4tTGjh1rnOdj0erUqROIyfv6669t4cKFKaPc8Ex55QUAvLsQBhLgNGvWLOvZs6fRbti0a9fOHWfA5pprrrGNGzfaN99846xbHGBbyo1DpC8REAEREAERCEsgKZUbXFq8WwvuY8TdPProo0bQsR/5DNvaKOzE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ERABERABEShIBJJSudmwYYNVqFDB7r77bhcXQ4erY8eOdu6550btt6PzxmfJkiXG7OiXXHJJ2LI//vhjt/+0004LezyZdhK71KRJE9dpnT59ut1yyy22cOFCe+WVV6TcJNMPGaO6ony8//77rvTTTz/dLYmFIa4NVzUsmlgxcRd99tlnrUyZMq5j36dPHzvhhBOsf//+7rl95513XBxcsHKzb98+dxwF6fHHH08YxYZGDh8+3A2msI7F6sEHH2TVyZYtW+yTTz4xnn+sybSDc5A//elPduuttzrlh3fWo48+6hQ6ePz+++/OtfXdd9+13r1729atW+3pp592DGvUqOGu918okyNGjLDrrrtOio2HoqUIiIAIiIAIRCCQlMrNrl273Egpo55YG+gs0SFq1KiRFS9ePEJTc7abZAEfffSRuw9WC2J6QoUOy/jx4619+/bONS70eDJuM+LOSDXKTYsWLezXX3+1RYsWJWNTVOcoErjiiiucSyZFXnnllc4KwTpxKXTi//KXvziLQ7ly5Zz1YfHixe654DgdeTr+PvYMy06w8Dd2++23u/PGjh2bUIoN9cQyc9xxx7kqYykOFazIWHBCLS6XXXaZs4RideGdBRuSlARL586drXnz5m4XCQRWrFhhwcoNsW2ff/6544NlRyICIiACIiACIpA5gaRUbrwC06tXL+cyduONNzrXMJSRdu3aZd7ibB5FYeHz73//242Y/v3vf7f7778/cDVWDkZgUQZuvvnmwP5kX2E0nc4awpJRaOKMJAWbAJ13OugTJ040rJW4YyIk3sBlzXfsGQRA2dm+fbs7jkUClyyv2LidIV+4aCG4ZpGFsGjRoiFnxHcTZS6SWxo1a9asWaD9wTUdNWqUffjhh85qBSPad8wxxwSfks7FlXaj6AUL1mN4ouBIuQkmo3UREAEREAERCE8g4zBk+PMSai8uaQj/9BHfscJ9LNpSpEgRI0MUI6peUGywGDVs2NApPP7+/niqLGlXqrYtVX6j/GrHySefbC1btnSxMMSXEfeG0Onftm1boBooJ1hqSpcu7fYRg0PmL+JoIgnua1gtsBg+8MADgQyIkc5PtP3hlDFicIilueuuuwxrFC55xNoQw5YT4XoyNeKahgubRAREQAREQAREIHMCSanc4N+P0CHCR5/AXSQaMTc//vijizHBl56OGm41zMXhg6cJtkexIXtSly5d3Fw3uJrs3LnT1UFfIpDKBIhvO+uss1xMDUoLLlVY9ojL4nkhqyDiY3LOPvtst00Hnw4/80KhGAXPD4U1g4EKkgiQkY0Yl2QXb+lB8YMTrrO8U3IquMNhHSamkKQM3333XU6L0PkiIAIiIAIiUKAIJKVbGj7wzEXzzDPP2IwZM9wPhn+771Dk5Reko0Z6Z99JwzWLDlr37t1dsatWrXKdFTosjKp6ufjii10Avt9OxqW3hCVj3VXn/CPAs0CiCeJBUPCZ84XnxT8zpEwngQBSsWJFZ40hJo7geqRYsWIuAYHbOPzlrYOlSpVyVgosFZQd7bmx/P2iuaTu4eJwaCNzXz3//PP23HPPGRZgYvdQAIPFt519lBO8zT6/ff311xvJG7BskawBK5BEBERABERABEQgI4EjDs+2/Z+MuzPu4aS0w18HDn/tOZRmO/an2aZ9h6xu2m6rWum/bmIZrzKXIpZJPCdNmhTucJ72kb2JkVHcWvDrD5Wvv/7a+vbt6xSgUF/30HNDt+mE4CMfHE8Qek5W28T/MOFnNNNUZ3XP/Dqeym3LL4apdB/veobLqM8WFto+XNNwWSOw3nfaQ8+JtE2q6Vi9RyLdMxr7eUdh1Q2XkCQa5eflHReN+6sMERABERABEUg0AklpufEQ6URlFqjsz8vNklFkPhIREIGsCWSVNIASGIQoaMI7KlaKTUFjqfaKgAiIgAiIQHYIJGXMTXYapnNEQAREQAREQAREQAREQAQKFoGYu6WRwQxXlHBuY7FGjccdqZzDZTOK9b0pn3vT7lSMZUnltuXH34bukTMC8XyP5Kym+Xt2vN9x+dta3U0EREAEREAEsiYQc7c05k0Jnok86ypF7wz8+uOl2NAKgohTVVK5ban6myVzu+L5HklkbvF+xyUyG9VNBERABESgYBKQW1rB/N3VahEQAREQAREQAREQARFIOQJSblLuJ1WDREAEREAEREAEREAERKBgEpByUzB/d7VaBERABERABERABERABFKOgJSblPtJ1SAREAEREAEREAEREAERKJgEYq7cMKs2s41HU8gQlJaWluETOh8pkwZOmDDBZWvL6f3JBrZ27Vr77bffIl76ww8/GJP0RZKnnnrKNm/eHOlwQu9nUsb333/fpk2bFrYNydy2hAafhJXzz2Lo8xfNpsTiPZLd+tG+jRs32q5du7J7Sb6dl5d3XL5VUjcSAREQAREQgXwkEPNsaatXr3Yzi3fo0CFqzerfv7+tXLkyQ3k9e/a09u3bB/bzj//tt9+2Hj16ZDsdM2mr77rrLluzZk2gnMaNGxv3POaYY9y+2bNn23PPPWe//PKLK7dly5Z29913Z5h1HaWuQYMGMZtoNFDBKK/s27fPunTpYscdd5xVqlTJGjZsmKENydq2KKMq8MUtWLDARowYEeDApLoXXHCB8bxHM0tiLN4jgUpnsvLKK6/YlClTAmfUrFnTRo0aleFZD5yQzyu5ecflcxV1OxEQAREQARHIVwIxV25i0RoUCSwrXj788EObMWOGnXHGGX5XrpeMPp9wwgmGokTHftmyZa4zQwfn5ptvtp07d9qYMWOsdevWbnvVqlU2aNAga968ubVo0SLX902kCxctWuSqg9UmFefoSSTWyV4Xb60ZNmyYm9Np8eLFThngOenVq1dSN4+BEZ572nH++ec7y80LL7yQ1G1S5UVABERABEQg1QkkpXJTtmxZ4+Pl448/tvr161uZMmX8rlwvCxUqZH379g1c36pVK5s6daqtX7/e7duzZ49bNmnSxAoXLuysGigAWHqSXblZunSpU9xoIxYsLF5I79697fTTT3fr+hKBcAQqVqzonr+TTjrJ5s6daytWrHCnMfkmrqHz58831mvVquWeL85H+vTpY5UrVzbcznbv3m316tWzfv36WYkSJdzxeH5hncVq265dO1cNLJnB7wZ2DhkyxKpXr+7c1pYvX+7Ou++++9x1L7/8sr355pvOwstkvk2bNrU77rjDvTc4EZfWF1980WbNmuXOKVasmBtUOffcc105S5YssUmTJtmPP/5oxx9/vF1zzTXWpk0bd0xfIiACIiACIiAC4QkkpXIT3JTvvvvOxYTccMMNwbujtk7MzM8//+zcbCj0xBNPdB2Xf/zjH7Zp0ybnHody07Zt26jdM14FnXLKKTZ06FD75z//abjesY4EK5LxqpvumxwEcJPig6UDmT59uvtbuv76650lFEWHzv/TTz/tXLu2bt1qPMM33nijlS5d2kaPHm1YR+Jt9cEyzAeXzMxky5Yt9sknn9hpp51m9957r+HSyQAJwnvh1ltvdcrPhg0b7NFHH3Wueih0yLPPPuviES+77DLHi7J41yC44T388MNOmUGh4R5YjBs1aqTn0RHSlwiIgAiIgAiEJ5D0ys2rr75qjHgywhptoaMyYMAAq1q1akC54R4nn3yyc1d77bXXXAeIjlzRokWjfft8L4/RZVzySpUq5TphrEtEIDsEBg4c6Kx9dNB5Hq+66ip32bx586xu3brWqVMnt02c2tixY43ziM9B6tSpE3i+vv76a1u4cGHclRuvZBQvXtzVEVdNLDEIAykMBHg5+uijnQXniCOO8LvcEqWFxBxffvmlc2krV65culi+t956y7AAewspAydeeLegHNWuXdvWrVvnFBru42MI/XlaioAIiIAIiIAIpCeQ1MrNr7/+6jpCdKRCOxbpm5nzLTol99xzjxFTQMD0kUf+N7Ec7mfEojD6SuA0nbXrrrvO6AQx+iwRgYJIgKQaKMW4h+Jm5oUMY8HumritITt27AgoN3TgvRCwP3PmTNu7d69LaOH35/fSu7jyjkFo0znnnGOTJ092cXfB9WnWrFnY9w+JB4gHZJCA62mTT0rCOq6fkdw9sWiRkOHTTz8N3ApFCPc0iQiIgAiIgAiIQGQCSa3cvPPOO65lF110UeQW5uKIV2yIPaGDwki0F+9Xj/88QmejWrVqRryKlBtPScuCRoAEG14hCG47zw4uWV78esmSJf2udMdx9eQa4lviKcTTFSlSxHjeyfyGVaV8+fJOuQmtVzirLamjP/jgAzdAglKEkKTEp46nfVhmSEjiY3qCy8XKs23bNpelMXi/1kVABERABERABDInEPN5bjK/fe6PYlEhHTFKRjRHM+l8EDRM5+T22293o7RYa/x8NSgyCHEBzIFD4HQqJBNwjdKXCESZAM8nyQIIjucZev31153S4F3SuB0dfAYHmFdqzpw5hiUkEYQAfp5v3MdwUcWVLrvilTcUFAZLGIgJvf6ss84y3N2YT+rAgQPunYNLHnLhhRfawYMH7fnnn3fWYcogKcO3336b3SroPBEQAREQAREokASS1nJDhjQCfjt27BjVHw43M58ZjRTPXsjuROYiOmvdunVzGY7IhMToK52Uyy+/3J+a9Mtou/glPRA1ICKBrP5WiCchZoTgeIS4rsGDBweC7tlXoUIFt491rDadO3dmNe6CRYV3DBPWTpw40dWHbG+43nmh/d5l1e9jSTtwl0U5IesaViAywflYHs5h8ASlhiQKfBCfGOXMM8+07t27u2spAyHm5sEH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bvF+xyUyG9VNBERABESgYBJIPzNlwWSgVouACIiACIiACIiACIiACKQAASk3KfAjqgkiIAIiIAIiIAIiIAIiIAJmUm70VyACIiACIiACIiACIiACIpASBKTcpMTPqEaIgAiIgAiIgAiIgAiIgAjEXLn54osv7I033ogJabKw/fjjjxYp4dtPP/1kEyZMcNnacloBZhQnFSuTD0aSH374wQ4dOhTpsD311FPGJHypKKnctnj+Xr/++qsxx8mBAwcC1di0aZP7W4L5iy++GNif3ytff/21ffDBB1G97SeffBJo20cffRSx7Fi+RyLeNBsHePekpaUFPtm4JFunHDx40KZNm2a8YzKTvLzjMitXx0RABERABEQgWQnEPFva6tWrbc6cOdahQ4ccMeKfOh25efPmuVnLJ06cmO76d99918aNG+f2HX300fbggw/aqaeemu4c/vEzCV6PHj1ylI6Z83fs2BEoi3IHDBgQSGc9e/Zse+655wwFiDTPzNB+9913Z5h5HaWuQYMGVqVKlUBZqbKSym2Lx2/E3/ugQYNsy5YtgdsPHDjQmjZtanR0UeJRenbt2mVdunQJnJOfK4sXL7b58+dbq1atonZbUorTthUrVriO/Nlnnx227Ny+R8IWFsWd/fr1M+rmhfdU5cqV/Waul/v373fvvzp16mQ6uWlu33G5rpguFAEREAEREIEEJxBzy01u2s9o6E033WRr1661MmXKZLCOYA1BsaGTh2WmQoUKRkcweLQ7N/f111x77bWu3BdeeMHuu+8++/zzz23KlCnu8M6dO23MmDHWpEkTe+WVV5zSgwK2cOFCf7mWIpAjAszhcuedd7qU6fw983fVv3//gDJds2ZNu//+++2iiy7KUbnRPpkBioceeiiqxZ533nmubXTik1HuueceGzt2rHtfJWP9VWcREAEREAERSDUCMbfc5AYYczfQYahWrZoNGzbMvv/++3TFzJo1y1lMmLl77969tm3bNud6tmjRIqOzlFcJLuOMM85wnUysNMiePXvcEuWmcOHC1rBhQ1cXXNhatGjhjiXrFx1sRoyxIpx55pn21VdfOXcbRqfr1auXrM1K+HpjCcSC8cQTTwRG/c8666xs13vZsmWGxYBZ7ZFatWrZXXfdFSjrt99+s/Hjx9unn37q7lO2bFkbPHiwVa1a1Z3/3XffOeX9yy+/dNs8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Uh5FQAREQAREQAREQAREQARyJBB35WbhwoVutfEcc5KPABkZGRZqLVIWwBs5cqRbODC30W/ZssWWLVtmW7duze2pgfDPP/+8rV69OvA/lTZYlPGDDz6wl19+OWQZUrlsqXQdEp1Xniuer3DPWH7zN2/ePPv3v/+dYzQFUY/kmIkkDJCfOi4Ji6MsiYAIiIAIiEC+CcRduVm8eLG9/fbbuc7ounXr7G9/+5tdcskl1r1797Dnjx8/3i6++GIbM2bMfmF48U+dOtX27du337FIO7p27WrXXHON9erVy66++mrr16+f0dj3Em3eJk2aZGvXrvWnpczvrl27rEOHDvaPf/zD5s+fbyh62SVVy5a9HPofmcDkyZPd88UzdtFFF9m9995r06ZNi3xSLo5+/PHHNnHixBzPyGs9kmPEeQxAvdS2bVv74YcfAjG89NJLbt/vv/8e2BfvjbzWcfHOl+IXAREQAREQgUQRiPtU0HkpGL3F3bp1sxo1arhVzcMpJz/++KO99tpreUki4jmdO3e22rVr2+GHH25ff/21DR482MaOHeuUrGjzFjGBJD/46aefuhxitUnHNXqSHH9SZm/48OG2Y8cO11lAp0PRokXt9NNPT8q8FkSmvAIzevRoe/zxx12Sfl8oK3JB5ElpiIAIiIAIiIAImCWlcsPaDcOGDbPq1avbkCFDbOnSpftdKxSeBx980FBEsN7EUs4888xAdKeccopbiNNbL6LJW+DkFNuYO3euPfvss64RS0MNCxaCBatJkyYpVhplN5YEKleu7BSamjVr2qxZs2zRokUB5Qbr6L/+9S9n4TvyyCPtlltusaZNmwaSX758uaEc+ee4bt269sgjj+y3/hXP9BNPPGFYIwYNGpTQNaoCmY+wQecLFqUlS5bYcccdt19IyvHkk0+643QStGjRwm699VZXnxCY8l5//fXWqVMne++994zOGjpUnn76aXeMThX28SweddRR1qNHD2vWrNl+6WiHCIiACIiACIjAXwTi7pb2V1K520KxiSQvvviiFS9e3C677LJIwfJ8jMYbDbY777zTWB0dtxwvOeXNh0u134YNG7pGZ5s2baxYsWJum0Zo/fr1U60oym8cCNAYZxwW4pVdxsIwro1GNx0RFStWtIEDBzoFhXB0CvAM7dmzx/r27WtDhw61o48+er9xcLhC9u7d27777jsX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kG4E4q7c5AUYPcDdunWzGjVquFXN9+3bFzKaJ554wg455BB3LFZTE5N21apVrXv37lapUiWbN2+ePf3000Yj6KabbnKWHBSbK664wtq1a2fvv/++jRs3zho3bmz16tULmU/tFIF0JeCtNUOGDHFrOs2ePds9K1gzevTokdTFppMExebCCy+0888/38aMGWP/+7//6zokypUrZzVr1rR+/frZG2+8Yf/85z/DlqVu3bo2dOjQhE47HzZzOiACIiACIiAChYxAUio3rN0wbNgwq169utFoWrp0acjLQi8xSk2RIrHzritatKj17t07kN4555zjlBdc0BAsSQi9uCzQiZKDsvPBBx+kvHJzxx13GO52WM1atGhhS5Ysca5/ffr0sfr167ty60sEQhGoWLGi64ioXbu2ffTRR4bigOCqhWvojBkz3HatWrXc80V45M4777TKlSsbbmdbt2519xn32+GHH+6Ox/Prvffec/XHzTff7JK59957rUOHDvbhhx+635zS3rVrl/Xs2TMQbPjw4VaiRAn3n04R6rBnn33WSpYs6fZ9++23zrpMp8xRRx1lK1eudEoR9RtWLuqaG2+80aiDkG+++cZZxW644QZXB2FF4jkMtpS5gPoSAREQAREQAREIEIidVhCIMjYbKDY5yVVXXWUXX3yxs7LQcIiH4HLyyy+/GK5pSKlSpdwvDRsE1xVc2Wh4pLpQVqxlKG64G1155ZWGAhnstpPqZVT+40sANyk+WDMQFH+sm7ht4tqJRee+++4zb/FZu3at6zC47LLLnNLz/fffG26fBSHr16931lmfFh0ldFiwPxoh7IABA6x9+/a2adOmLGN2GjRoYLiy4ZLrBRfZAw880Ck2jOmjMwF5+OGHDQXr3XffdUqgD79z505X94wYMcIpPig1WIglIiACIiACIiAC4QkkrXITPsvmxuBgXeGlf9tttxmKBg0mGgyxFOJlbE+1atUCY4ZOOukklwQ9sgsWLHA9r+wIN/FALPNTEHGhxHlFrmXLltawYUPXIC2ItJVG6hKgkY8rZ5cuXZyl4uqrr3aFwdKJuybKy8knn+wUZxSBNWvWBAqLIsSYvFatWrlxK3PmzAkci+fGtm3bApYWnw4WlGifZSzMdAbgvppdUHywfqKwIFiw6DC44IIL3P+ZM2e6jpEmTZoYCh6ut9Qzb775pjse/AVLOnKw2vArEQEREAEREAERCE8gKd3Swmf3zyO4tHi3FhoEjLt5/PHHjUHHzZo1y+n0qI7jnnXPPfe4HmZ6TL3rG42WRx55xCZPnmwPPvigHXvssYYrTkG40USV8XwGonFXrFgxFwu/uMhEmhEun8np9DQhgGJStmxZw2KBm5kXrBcoyV54VhAUHD/RRp06dfxhN86FZwurhXfxChyM8QZWWNIJFmZO89bZ4P152WYsD65uzLrGzItYeFu3bu2i8sods7TxQVCSqF+yyymnnJJ9l/6LgAiIgAiIgAiEIZCSyk32svgGAY2HWIhXbHbs2OEmE/A+8z7uRo0aGR+EHlfcuJitLd2Enmk+EhHIiQCN9iOOOGK/YDw7vvHOQb9dpkyZQFi/jx0oApwTb8WGtFAm5s6dy6YTLDko8r7jxO9Hyc/LNM8oepSFsT2Mq6GOKF26tIu2fPny7veaa65xE5j4tEL9Zq9/QoXRPhEQAREQAREQgT8JJK1bGn77TMfMIGN6U9lmoDuCH/uiRYuMxggDd5nlCF/2WMxWhrKCyxuDfW+99VbnkkXawevV4DbDOBzG2WDVQRm65JJL/iSqbxEQgQCB5s2bu8kCeGZ4hnC7YmyLt9oQkIHzKBlYN3i2vetnIJI4bTB1M88u05rTkfHCCy+4lJjKOliwBtNxgosZzz31EcL6Pig9XvHx2348EWFwQ2OsDWVs27Ytu5zgskadRZrUNdQ71Gm4rklEQAREQAREQATyTiBpLTcoLMEv+l69ejmfdMbZMLNS8EBdejZxEYuFO8mWLVvcuhYgHThwYIAsvbmjR492/5n6mQYJ4t3UcMlJdaGxJRGB3BDIybLHGlM//vijGzRPvDwvgwYNCswIxj7WsWIfwrPMRBYFIQzOZ7ZDFunkw/1Px0b2Z5mZzbCwvJi5lg0zojHFNYoK20yW4KVz585u85ZbbnFTS/PnvPPOs1dffdW5egYrbVi5qF8effTRLDOuXXvttT46WU0DJLQhAiIgAiIgAtETOCCzl/GPaIITKCPza0/m1459GbZpd4at2rXP6mVstWqVKoSNgpW9UUS8YhA2YC4PMOiXmZdoDLEmRShhBjUaKxMnTgwswBkqXG73gYwZlRiPcuSRR4Y9nQYQA63pvU43Seeypdu1SobyYIFlfAuKTLBCxNTLrBfFB6tI9uM+7/GqR4gfi8vGjRudO5ofW+fTLYhf6jEsSChRfhroaNONVx0XbfoKJwIiIAIiIALJRiBpLTc5gUKp4ZMIoXGW3S8/EflQmiKQKgSYcCPSpBvFixdP2DPFJBrBkyAUNNNwnTMFnQ+lJwIiIAIiIALpQCBpx9ykA1yVQQREIDKBE088MaGKReTc6agIiIAIiIAIiECqEYi7WxouHwzG9TOaFSQg3MdY++awww4ryGQDaZE25U7HsSzpXLbABdRG0hBIZD2SNBBCZCTRdVyILGmXCIiACIiACCSUQNzd0nD54JMIwX0sUYoN5WVWqHSVdC5bul6zVC5XIuuRZOaW6DoumdkobyIgAiIgAoWTgNzSCud1V6lFQAREQAREQAREQAREIO0ISLlJu0uqAomACIiACIiACIiACIhA4SQg5aZwXneVWgREQAREQAREQAREQATSjoCUm7S7pCqQCIiACIiACIiACIiACBROAnFXbhYuXGiTJk2KG91169bZ6tWrLdRapP/9739t5MiRbra23GZgy5YttmzZMmPxwXBC2vv27Qt32K16Tt7SUVjRPV3Llo7XK55lysjIMD6hnsFYpRvveiRW+QyOBx6eTfD+WG7np46LZT4UlwiIgAiIgAgkC4G4z5a2ePFimzZtml166aW5KjOKA6uST58+3a1aPmrUqCznL1q0yAYOHOhW9uYAq3uPGTMmSxhe/FOnTrWuXbvmajpmwm/atCkQV6NGjax///6B6azff/99e+mllwwFiGmeW7VqZXfffXeWldc5GaXu+OOPtypVqgTiSpeNdC5bulyjgijHrFmz7LHHHgskxb1+9tlnu+c9lrMk5rUeCWSsgDfo9Aiu85gS/tRTT7XOnTtbLBftzGsdV8A4lJwIiIAIiIAIFBiBuCs3eSkJPZ7dunWzGjVq2BFHHLGfdQRryr333mu1a9e2a665xmrWrGnff/99XpIKeQ4NEOJmRfWvv/7aBg8ebGPHjrXu3bvbzz//bM8++6y1bt3abrrpJvvmm2+cknXyySdby5YtQ8annSKQrgS8tWbIkCFO+Z89e7Z7VnhOevToka7Fjrpcl112mZ177rnOyjl06FDbu3ev9enTJ+rzFVAEREAEREAERCB3BJJSuWHthmHDhln16tWNRtPSpUuzlGrKlClWrFgxe+KJJ6xIkSLu07x58yxh8vPnzDPPDJx+yimnuEYb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65H+97//tZEjR7rZ2nKbPrOBLV++3LZv3x721E2bNhkLiYaT559/3lavXh3ucFLvZ1HBDz74wF5++eWQZUjlsiU1+BTMnH8Wsz9/sSxKPOoRnz9fn/j//nfVqlXGfc6HRUTDCYtwvv7668aMbnkRFuL88MMP83Kq5aeOy1OCOkkEREAEREAEkpxA3GdLW7x4sU2bNs0uvfTSXKFYt26da1BMnz7drVo+atSowPl9+/a1RYsWBf77je7du9tFF13k/7oX/9SpU61r165RT8fMtNV33XWXLVu2LBBP06ZNjTRZaBD5/vvv7b777nOrivP/uuuusyuvvJLNLDJp0iQ7/vjj47bQaJbEYvhn165d1rFjRytRooRVqlTJGjduvF8ZUrVsMcSkqDIJzJo1yx577LEACxbVPfvss93zHstZEvNajwQyFmHj8ccfd+Xo0qWLXXHFFYGQe/futQ0bNthPP/1kmzdvds9E4GDQBh0YL774orVp0yZPM0POnj3bZsyYYeecc05QrNFtotzkto6LLmaFEgEREAEREIHUJBB35SYvWOhJ7datm9WoUcOOOOKI/awjd999t1u/xsf9n//8xyZOnGinnHKK35XnX9KuWrWqoSjRsJ83b549/fTTNnbsWLvpppuctah///5Wq1Yt6927t2vUcKx+/fpWr169PKebTCd++umnLjtYbdJxjZ5kYp3qefHWmiFDhrg1nWio8zxgzejRo0fSFw+r02effebu848++iiLclOzZk3r16+fvfHGG/bPf/4zbFnq1q1rQ4cOzfO083T85EWxCZshHRABERABERCBQkwgKZUb1m4YNmyYVa9e3Wg0LV26NMslOvLII42PFxonDRo0cIqQ35fX36JFizqlxZ9Po2PcuHGu95Z9X331lVOsrr/+epfemjVrXNB33nkn5ZWbuXPn2rPPPms7duxwrnxYvJBevXpZkyZN3La+RCAUgYoVK7rnoXbt2oaSMH/+fBcMVy1cQ7FMsO07BQiP3HnnnVa5cmXD7Wzr1q2uk6BPnz52+OGHu+Px/uJ5xkJz6623ujrnl19+sbJly0aVLBbOnj17BsIOHz7cWTsDOzI3sGxh1cECVKxYMTv//PNdxw1hFixY4JQitqtVq2aDBg1i08nGjRtt8ODB9uOPP7pn8aijjnLKYrNmzXwQ/YqACIiACIiACIQgEPcx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pZdemiswrOjNquTTp093q5aPGjUqy/nDhw938e7du9fNStapUye75JJLsoThxT916lTr2rVrrqZjJjyKi5dGjRpZ//79A6uEf//993bfffcZDRTkuuuusyuvvNIHD/yi1B1//PFWpUqVwL502UjnsqXLNYpFOSZPnuyUdB9XgwYNrHXr1nb22Wf7Xfn6/fjjj23JkiV27rnnRownr/VIxEhzOHjzzTcHOieYzv2EE06w9u3bW7169XI488/Dr776ql1wwQUhw+/cudPVb02aNLGyZctGFV+oQHmt40LFpX0iIAIiIAIikA4E4m65yQskeky7detmy5cvN1bozm4doUH07rvv2i233GKvv/66nXfeeTZ69Oh894L6vHbu3NlZfF555RWnxHz11Vc2duxYd5gebBSdWrVq2YsvvmhnnHGGOxauh9bHqV8RSGUCdCY89thjVqZMGfvb3/5mM2bMSOXiRJ33unXrurrgwQcfNBSSwYMHOytW1BEooAiIgAiIgAiIQIESiLvlJi+lYe2GYcOGWfXq1W3IkCG2dOnSLNHQW4mccsopzppy0kkn2cSJE23NmjX56gX1iZx55pl+M5AGbmoIig4LWF5//fVO8SJN5J133gnZQ+sOpsjXHXfc4axRWM1atGjhetRR5vr06WP169dPkVIom/EgULlyZStatKjVrFnTZs2aZYsWLbLTTz/dJYV1FCsrz8iRRx7pOh2aNm0ayAadFChH/jlGYXjkkUf2W/+KTownnnjCeL4HDRqU0DWqfOZLlCjhLK9YX3/88UdnxSJ/5cuXd/l88skn3XOCZYdn5tZbbw1YeIkDTl26dLHNmzfbsccea/fee69TEH38WEAHDhxoLFJKvYO1CM7sq1ChgvXo0cMHdQx//vlnGzBgQGCfNkRABERABERABLISSErLDVlEsQknNAKw6PCSf+2111xPcsWKFQ2XmVgJjRIabHfeeadreFx88cUuaq/M1KhRw/CpX79+vbGNQpDqwvglytKxY0fXgMXVjkbdhAkTUr1oyn8MCKB8fPDBBy4m3KkQxsIwrq1Zs2auI4LnkIa574BA4eEZ2rNnj/Xt29eGDh1qRx999H7j4Hbt2mW9e/e27777zoVL5OK7rmD//4ULKorYbbfdZi+88IJzP0WxQR566CFbsWKF3XPPPa6zA6Uvu/ssfHCXJcxPP/1k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+ZBxfP755+4Z51mYOXNmyDDsZAFPFuFkxrdYS37quFjnRfGJgAiIgAiIQDIQiPtsaYsXL7Zp06bZpZdemqvyrlu3zliVfPr06W5l71GjRgXOZ2rpAQMG2FdffeX2MdXywIED7YQTTgiEYYMXP4vgde3aNerpmIn7rrvucmtM+MiaNm1qffv2teLFixuL65E2yo0X1qG4+uqr/d/A76RJk+z444+3KlWqBPalwsauXbusY8eOVqJECatUqZI1btx4vzKkatlSgX8q5XHWrFn22GOPBbLMvX722We75z2WsySGq0dQap544gn77LPPAnm4/PLL3TNP58T48ePjtk4MHSAbNmyw+fPnG/XVaaedFshD8AYdHC+++KK1adMm5jNH5qWOC86btkVABERABEQg3QjEXbnJCzB6gLt162Y1atRwq5rv27cvSzRz5851is39999vderUsWHDhjnl5rXXXnMKSJbAufxD2lWrVrXu3bu7hv28efPs6aeftrFjx9pNN91ke/futbPOOst9DjnkEHv11Vdd73DdunUNJSgd5NNPP3XFwGqTjmv0pMM1SpYyeGvNkCFD3JpOs2fPds8K1ooePXrEPZuPPvqoLVq0yB555BHjGaTzAYtNQciZZ55pfPr16xcxOfI1dOjQhE5LHzGDOigCIiACIiACaUQgKZUb1m5AYalevbrRaFq6dGkW5O+9955VrlzZTj75ZLcfqwkuInxatWqVJWxu/xQtWtR69+4dOO2cc86xcePGuUYTO1Gm+Hi55pprjPzQqEp15Qal8dlnn7UdO3Y4Vz4sXkivXr2sSZMmvsj6FYH9CFSsWNF1RNSuXds++ugjZ80gEK5YuIbOmDHDbdeqVcs9X4RH7rzzTvcs43a2detWq1+/vvXp08cOP/xwdzzSFxYROh9uvvlma9SokQt67LHHGp9geemll2zy5MlOubj22mudZYnjGzdutMGDBzt3Niy2Rx11lFPImjVrFjh9woQJTnlCQZkyZYrt3LnTrrzySuO5z0mwgPbs2TMQbPjw4c4a6neghKH0UL9h5aKuufHGG406iGeR8FjBQuXdx6FfERABERABERCBrATiPuYma3LR/0OxCSc0fGg4eKExjuAiEmuhAYULWjjFxbvDZHeJi3U+CiK+hg0buh5w3GeKFSvmtukRp8EpEYFoCOAmxQdlAMEt7P33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K0Q07jp37uw+t912m11++eU2fvz4LHG8++67dskll9gNN9zgjn/11VdZjvOHF//UqVMjKiD7nRS0Y/v27S7uiy66KIuCRAPkiiuusK5du9qll15qEyZMCDrrr02UurVr1/61I4220rlsibhM69ats+7du1unTp3s1ltvdfcdDX9k7969Ton/7LPPXCM5EfkjzdmzZ9vLL78c0+SZUpwOivfee89mzJgRNu681CNhI/v/A3369LGLL77YeL6pb4YPH267du3K6bRcH6eD48UXX7Tdu3dHPPef//ynharHIp2U3zouUtw6JgIiIAIiIAKpSCDuyk1eoNCYO+uss2zEiBH2wgsv2HnnnWevvPKKzZs3z0VHY4GGSMeOHZ1lpkKFCjZgwIAsvd15STf7OU899VQWpYbj9PT279/fatWq5RosZ5xxho0dO9ZofElEIC8E6NG/44473JTpWBpRlvv27RtYG6pmzZrWr18/O//88/MSfczOQZF/6KGHYhYfEZ155pmubHXr1o1pvNFGVqVKFRs6dKhdd9119vHHH9vtt98ec0sOZSONRE5LHy0PhRMBERABERCBVCcQ93Vu8gKoTp06xsfLNddc43p2seg0bdrUbbMw5lVXXWU7d+609evXOyXk008/dY0lf15+fqdPn+4UFtII7q2mZ5Xe5uuvv96OOOIIW7NmjUvmnXfesXr16uUnyYSfSwOb3mWsCC1atLAlS5Y4ZY4e7vr16yc8f+magffff9/dUyjTlStXdsU89dRToy4uSv+oUaNs48aN7hwU77vuuisQFxZIOgq+/PJLl86RRx5pgwYNsmrVqrnwP/zwg1PQv/76a/e/evXqrjHuM7BgwYLAf87h3GDZunWr62SYO3euu38qVqzoFBbiySlvwfEkYptFdlEe+cDlvvvucxYq7v9IeefYsGHD7Nlnn7WSJUu6rOO2N3jwYHviiSfsqKOOclagnj17BopFh0yJEiUC/z/55BN77rnnjOvTpEmT/dzW5syZY6NHj3aWrdKlS9u1115rbdq0CZyvDREQAREQAREQgf0JJKXlJns2ccdBTjjhBPeLqxfWGhQcGoSNGzcO7Hcb+fxirM8zzzxjd999d5bGCNF6ZaZGjRo2ZcoUp1ixjUKQ6oJFjLJgEZs1a5ZdeeWVRs92OLe7VC9vsuR/6dKlzkrjFZvc5otxX5dd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2GEFnbRLj7Qpdyg3oYRkKIaJpnPZYohJUcWIQCLrkRgVIS7RJLqOi0uhFKkIiIAIiIAI5INA3N3SGCzNJxGC61iiFBvKm1f3okSwym2a6Vy23LJQ+PgTSGQ9Ev/S5T2FRNdxec+5zhQBERABERCB+BCQW1p8uCpWERABERABERABERABERCBAiYg5aaAgSs5ERABERABERABERABERCB+BCQchMfropVBERABERABERABERABESggAlIuSlg4EpOBERABERABERABERABEQgPgSk3MSHq2IVAREQAREQAREQAREQAREoYAJSbgoYuJITAREQAREQAREQAREQARGID4G4KzebN2+2n3/+OT65zyHW3bt325o1a3IIFZ/DrD+xcuVK27dvX3wSSGCs6Vy2BGJV0hEIJLIeiZCthB9KZB2X8MIrAyIgAiIgAiIQgkDclZtx48bZ8OHDQyQd/11z5861Xr16xT+hECns2LHDevbsaevXrw9xNLV3pXPZUvvKpG/uE1mPJDPVRNZxycxFeRMBERABESi8BOKu3BRetCq5CIiACIiACIiACIiACIhAQRIoGo/Evv32W5s2bZqLesGCBfbbb78FrDennnqqbdu2zVasWBEy6QsvvNAWLVoU8Thx446RXVjF/IILLrCXXnrJHcItjHDecnT00Udby5YtbcqUKdlPdf+rVq1qlSpVss8++yzk8aZNm9qmTZsi5m3q1Km2fft227Nnj4tj/Pjxdthhh1nRokWte/fuNmHChLB5P++88/KVtwMPPDBfeY/EtWPHjkbvebiydejQIWnznhPXxo0b5yvvOd0T+bmf0znvOT1rBxxwQL7qkZzu51R9FnOq49q2bRuy/tJOERABERABESgMBOKi3DDOZNeuXY7f77//bnz8fxQdXLWWLl0aki9KQU7Hly9fbjt37tzv/GLFihnjQXxae/fudWGC/5N+uLRRQEqUKBH2eO3atXPMG2nx8coN2ygdfJBIec9v3ooUKZKvvEfKG3mPVLZkzntOeatfv35YbrG4J/JzP6dz3nN61ugU4J5D8lKP5HQ/RzqeE/ec8h7PZzGnOs4B05cIiIAIiIAIFFICB2S+KP+IpuwEysj82pP5tWNfhm3anWGrdu2zehlbrVqlCmGjGDp0qG3dutUGDBgQNky8DsyaNctI//XXX49XEmHjxcJx1VVX2ciRI61KlSphw6XigXQuWypej8KQ50TWI8nMN5F1XDJzUd5EQAREQAQKLwGNuSm8114lFwEREAEREAEREAEREIG0IhB3y826deucS0kirBdYGEi/Tp06BX7RMjIybMmSJYYrG+5y6STpXLZ0uk7pVJZE1iPJzDGRdVwyc1HeREAEREAECi+BuCs3hRetSi4CIiACIiACIiACIiACIlCQBOSWVpC0lZYIiIAIiIAIiIAIiIAIiEDcCEi5iRtaRSwCIiACIiACIiACIiACIlCQBKTcFCRtpSUCIiACIiACIiACIiACIhA3AlJu4oZWEYuACIiACIiACIiACIiACBQkASk3BUlbaYmACIiACIiACIiACIiACMSNgJSbuKFVxCIgAiIgAiIgAiIgAiIgAgVJQMpNQdJWWiIgAiIgAiIgAiIgAiIgAnEjIOUmbmgVsQiIgAiIgAiIgAiIgAiIQEESKFqQieUmrcmTJ9umTZvcKaeffrrVrl07N6fnK+zUqVPtmGOOsXr16uUrnpxOnjVrlpUqVcoaNWqUU9C0Of7rr7/ahg0b7Mgjj7SSJUuGLNeWLVvsoIMOskMPPTTkce0UAREQAREQAREQAREQgVAEkla5+eWXX1wjePbs2VaxYsUCVW5ee+01a926ddyVG9KpVatWgSs37777rm3evNk6deoU6p6Iy77ff//d7rrrLlu2bFkg/qZNm1rfvn2tePHibt+2bdvs3nvvtZUrV7r/zZs3t/vuu8+KFk3a2zRQFm2IgAiIgAiIgAiIgAgknkDSuqV17drV+vXrZwceeGDiKcUpBzTcr7766jjFHj7ab775xubOnRs+QByO/PHHH1a1alV7/PHH7eWXX7Y77rjD5s2bZ2PHjg2k9vTTTzula+TIkXbLLbfY559/bq+++mrguDZEQAREQAREQAREQAREIBKBuHWJ//e//7Unn3zSlixZ4hSUFi1a2K233moHH3ywbd++3TVezznnHPvwww9t586ddt5559kNN9xgBxxwQKT8umMDBw60ChUqWI8ePQJhhw8fbj///LMNGDAgsC/SxgsvvGAff/yxsw7hHtW9e3c744wzAqesXbvWevbsaWvWrLGTTz7ZpVWmTBl3HOXgmWeesfXr11uJEiWsXbt21r59e5s0aZLNmTPHHn300UA8bOzbt8+6devmyoy14l//+pe9//77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LNz0yDNWC5Qar9gwvia76xvWlS6ZkzEgWNC8pc7tCPOFAkhZESYp8MKkBczAhlA2XNaYHQ5B4Qhm5XZG+Armlj0YU4szUQGTNCAwg6NfQwfLGpYYriVjdbh2wco4kzww+58Xrmtu8ubP068IiIAIiIAIiIAIiED8CByQaT34c6R0DmkQKCPza0/m1459GbZpd4at2rXP6mVstWqVKoQ9m/Ea9HZ7t62wAfNwgBmsmPUMhYc0ciP0ujNTGQ3z7DOlRRMP1g4a+Sg7jAEqaMHqhMJFA5spjJn8IFaC+xhWKabb9o1/4sZt69lnn3VKAOyweMWj7Fh1UC5CuZflt4xMg82sapQtkjIUKh3Og0ulSpWycAkVVvtEQAREQAREQAREQAQKnkDcLDe+KExpHGvBAsAMafS0X3zxxblWbMgPypB3ScpL/nBLys/5eUmTc3D1w60K1ygsR1iCsLjEUrBq5FQ2rEbxkuxjfGKZDhMX+MkLchsvroZY+SQiIAIiIAIiIAIiIALJSSAuY27iXVSsQYzXYOFM3I0Kk9C4ZvpiBrZjfWLdlWjcwmLBCIUmeOxQLOJUHCIgAiIgAiIgAiIgAiIQKwJxd0uLVUYVjwiIgAiIgAiIgAiIgAiIgAhEIpCSlptIBdIxERABERABERABERABERCBwklAyk3hvO4qtQiIgAiIgAiIgAiIgAikHQEpN2l3SVUgERABERABERABERABESicBKTcFM7rrlKLgAiIgAiIgAiIgAiIQNoRkHKTdpdUBRIBERABERABERABERCBwklAyk3hvO4qtQiIgAiIgAiIgAiIgAikHQEpN2l3SVUgERABERABERABERABESicBKTcFM7rrlKLgAiIgAiIgAiIgAiIQNoRkHKTdpdUBRIBERABERABERABERCBwklAyk3hvO4qtQiIgAiIgAiIgAiIgAikHQEpN2l3SVUgERABERABERABERABESicBKTcFM7rrlKLgAiIgAiIgAiIgAiIQNoRkHKTdpdUBRIBERABERABERABERCBwklAyk3hvO4qtQiIgAiIgAiIgAiIgAikHQEpN2l3SVUgERABERABERABERABESicBPKl3BxQOJmp1CIgAiIgAiIgAiIgAiIgAklIIE/KjVNqpNkk4eVUlkRABERABERABERABESg8BLItXLjdRpOLOL/FF5+KrkI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" + } + }, + "cell_type": "markdown", + "id": "c303d698-2f44-4f6f-8ce5-6a4f9f13534a", + "metadata": {}, + "source": [ + "![image.png](attachment:f7ad0425-26fe-482c-b97c-c9493b05fbf2.png)" + ] + }, + { + "cell_type": "markdown", + "id": "abd854e5", + "metadata": {}, + "source": [ + "## Create a data processing function\n", + "\n", + "The following code demonstrates how to create a simple data processing function using MLRun.
\n", + "The function will process the data and show some statistics.
\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4e759f9-7154-4397-8db3-93b808426bd1", + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile process_data.py\n", + "\n", + "\n", + "# Here is an example of Spark processing.\n", + "from pyspark.sql import SparkSession\n", + "from pyspark.sql.functions import avg, min, max\n", + "import pandas as pd\n", + "import json\n", + "import fsspec\n", + "\n", + "def process_data(data_path: str, data_output_path: str):\n", + " spark = SparkSession.builder.appName(\"MusicDemo\").getOrCreate()\n", + " spark_df = spark.read.parquet(data_path, header=True)\n", + " spark_df = spark_df.drop(\"name\", \"id\")\n", + " \n", + " music_stats = spark_df.groupBy(\"favorite_music_type\").agg(\n", + " avg(\"age\").alias(\"avg_age\"),\n", + " min(\"age\").alias(\"min_age\"),\n", + " max(\"age\").alias(\"max_age\")\n", + " )\n", + " music_stats.show()\n", + " pandas_df = spark_df.toPandas()\n", + " pandas_df.to_parquet(data_output_path)\n", + " # spark_df.write.mode(\"overwrite\").parquet(data_output_path)\n", + "\n", + " return {\"music_data\": data_output_path}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "13748b64-6a48-4500-a2a8-d9290dd082c5", + "metadata": {}, + "outputs": [], + "source": [ + "process_data_function = project.set_function(\n", + " func=\"./zeev-demos/mlflow-databricks/process_data.py\",\n", + " name=\"process-data\",\n", + " kind=\"databricks\",\n", + " image=\"mlrun/mlrun\",\n", + ")\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "2dbadf07-a32a-40da-b9bc-609070e4392d", + "metadata": {}, + "source": [ + "Set all parameters necessary for the function and run it." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5642aa15-e8c0-4a72-a0a8-4cacd34fb63c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:34:45,422 [info] Storing function: {'name': 'process-data-process-data', 'uid': 'a9c770f8377046bda3061e61a5c015c2', 'db': 'http://mlrun-api:8080'}\n", + "> 2024-03-27 15:34:45,675 [info] Job is running in the background, pod: process-data-process-data-89bhh\n", + "> 2024-03-27 15:34:49,272 [info] Running with an existing cluster: {'cluster_id': '0327-134616-43m7kfxk'}\n", + "> 2024-03-27 15:34:49,492 [info] Starting to poll: 493449112310004\n", + "> 2024-03-27 15:34:49,539 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.PENDING\n", + "> 2024-03-27 15:34:50,947 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.PENDING\n", + "> 2024-03-27 15:34:53,063 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.RUNNING\n", + "> 2024-03-27 15:34:56,737 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.RUNNING\n", + "> 2024-03-27 15:35:00,947 [info] Artifacts found. Run name: mlrun_task__15_34_48_703046\n", + "> 2024-03-27 15:35:01,881 [info] Job finished: https://dbc-94c947ab-feb9.cloud.databricks.com/?o=4658245941722457#job/499259196347814/run/493449112310004\n", + "> 2024-03-27 15:35:01,881 [info] Logs:\n", + "+-------------------+------------------+-------+-------+\n", + "|favorite_music_type| avg_age|min_age|max_age|\n", + "+-------------------+------------------+-------+-------+\n", + "| Rock| 30.125| 27| 34|\n", + "| Classical|47.666666666666664| 38| 75|\n", + "| Pop| 24.0| 18| 38|\n", + "+-------------------+------------------+-------+-------+\n", + "\n", + "2024-03-27 15:34:54,980 - mlrun_logger - INFO - successfully wrote artifact details to the artifact JSON file in DBFS - music_data : /dbfs/demos/mlrun_databricks_demo/music_output_new.parquet\n", + "> 2024-03-27 15:35:02,182 [info] To track results use the CLI: {'info_cmd': 'mlrun get run a9c770f8377046bda3061e61a5c015c2 -p mlflow-tracking-example-guy', 'logs_cmd': 'mlrun logs a9c770f8377046bda3061e61a5c015c2 -p mlflow-tracking-example-guy'}\n", + "> 2024-03-27 15:35:02,182 [info] Or click for UI: {'ui_url': 'https://dashboard.default-tenant.app.llm-dev.iguazio-cd1.com/mlprojects/mlflow-tracking-example-guy/jobs/monitor/a9c770f8377046bda3061e61a5c015c2/overview'}\n", + "> 2024-03-27 15:35:02,182 [info] Run execution finished: {'status': 'completed', 'name': 'process-data-process-data'}\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
mlflow-tracking-example-guy0Mar 27 15:34:48completedprocess-data-process-data
v3io_user=zeevr
kind=databricks
owner=zeevr
mlrun/client_version=1.6.1
mlrun/client_python_version=3.9.16
host=process-data-process-data-89bhh
task_parameters={'timeout_minutes': 15, 'spark_app_code': 'IAoKaW1wb3J0IG9zCmltcG9ydCBsb2dnaW5nCm1scnVuX2xvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCdtbHJ1bl9sb2dnZXInKQptbHJ1bl9sb2dnZXIuc2V0TGV2ZWwobG9nZ2luZy5ERUJVRykKCm1scnVuX2NvbnNvbGVfaGFuZGxlciA9IGxvZ2dpbmcuU3RyZWFtSGFuZGxlcigpCm1scnVuX2NvbnNvbGVfaGFuZGxlci5zZXRMZXZlbChsb2dnaW5nLkRFQlVHKQptbHJ1bl9mb3JtYXR0ZXIgPSBsb2dnaW5nLkZvcm1hdHRlcignJShhc2N0aW1lKXMgLSAlKG5hbWUpcyAtICUobGV2ZWxuYW1lKXMgLSAlKG1lc3NhZ2UpcycpCm1scnVuX2NvbnNvbGVfaGFuZGxlci5zZXRGb3JtYXR0ZXIobWxydW5fZm9ybWF0dGVyKQptbHJ1bl9sb2dnZXIuYWRkSGFuZGxlcihtbHJ1bl9jb25zb2xlX2hhbmRsZXIpCgptbHJ1bl9kZWZhdWx0X2FydGlmYWN0X3RlbXBsYXRlID0gJ21scnVuX3JldHVybl92YWx1ZV8nCm1scnVuX2FydGlmYWN0X2luZGV4ID0gMAoKCmRlZiBtbHJ1bl9sb2dfYXJ0aWZhY3QobmFtZT0nJywgcGF0aD0nJyk6CiAgICBnbG9iYWwgbWxydW5fYXJ0aWZhY3RfaW5kZXgKICAgIG1scnVuX2FydGlmYWN0X2luZGV4Kz0xICAjICBieSBob3cgbWFueSBhcnRpZmFjdHMgd2UgdHJpZWQgdG8gbG9nLCBub3QgaG93IG1hbnkgc3VjY2VlZC4KICAgIGlmIG5hbWUgaXMgTm9uZSBvciBuYW1lID09ICcnOgogICAgICAgIG5hbWUgPSBmJ3ttbHJ1bl9kZWZhdWx0X2FydGlmYWN0X3RlbXBsYXRlfXttbHJ1bl9hcnRpZmFjdF9pbmRleH0nCiAgICBpZiBub3QgcGF0aDoKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZidwYXRoIHJlcXVpcmVkIGZvciBsb2dnaW5nIGFuIG1scnVuIGFydGlmYWN0IC0ge25hbWV9IDoge3BhdGh9JykKICAgICAgICByZXR1cm4KICAgIGlmIG5vdCBpc2luc3RhbmNlKG5hbWUsIHN0cikgb3Igbm90IGlzaW5zdGFuY2UocGF0aCwgc3RyKToKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZiduYW1lIGFuZCBwYXRoIG11c3QgYmUgaW4gc3RyaW5nIHR5cGUgZm9yIGxvZ2dpbmcgYW4gbWxydW4gYXJ0aWZhY3QgLSB7bmFtZX0gOiB7cGF0aH0nKQogICAgICAgIHJldHVybgogICAgaWYgbm90IHBhdGguc3RhcnRzd2l0aCgnL2RiZnMnKSBhbmQgbm90IHBhdGguc3RhcnRzd2l0aCgnZGJmczovJyk6CiAgICAgICAgbWxydW5fbG9nZ2VyLmVycm9yKGYncGF0aCBmb3IgYW4gbWxydW4gYXJ0aWZhY3QgbXVzdCBzdGFydCB3aXRoIC9kYmZzIG9yIGRiZnM6LyAtIHtuYW1lfSA6IHtwYXRofScpCiAgICAgICAgcmV0dXJuCiAgICBtbHJ1bl9hcnRpZmFjdHNfcGF0aCA9ICcvZGJmcy9tbHJ1bl9kYXRhYnJpY2tzX3J1bnRpbWUvYXJ0aWZhY3RzX2RpY3Rpb25hcmllcy9tbHJ1bl9hcnRpZmFjdF9hOWM3NzBmODM3NzA0NmJkYTMwNjFlNjFhNWMwMTVjMi5qc29uJwogICAgdHJ5OgogICAgICAgIG5ld19kYXRhID0ge25hbWU6cGF0aH0KICAgICAgICBpZiBvcy5wYXRoLmV4aXN0cyhtbHJ1bl9hcnRpZmFjdHNfcGF0aCk6CiAgICAgICAgICAgIHdpdGggb3BlbihtbHJ1bl9hcnRpZmFjdHNfcGF0aCwgJ3IrJykgYXMganNvbl9maWxlOgogICAgICAgICAgICAgICAgZXhpc3RpbmdfZGF0YSA9IGpzb24ubG9hZChqc29uX2ZpbGUpCiAgICAgICAgICAgICAgICBleGlzdGluZ19kYXRhLnVwZGF0ZShuZXdfZGF0YSkKICAgICAgICAgICAgICAgIGpzb25fZmlsZS5zZWVrKDApCiAgICAgICAgICAgICAgICBqc29uLmR1bXAoZXhpc3RpbmdfZGF0YSwganNvbl9maWxlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHBhcmVudF9kaXIgPSBvcy5wYXRoLmRpcm5hbWUobWxydW5fYXJ0aWZhY3RzX3BhdGgpCiAgICAgICAgICAgIGlmIHBhcmVudF9kaXIgIT0gJy9kYmZzJzoKICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKHBhcmVudF9kaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihtbHJ1bl9hcnRpZmFjdHNfcGF0aCwgJ3cnKSBhcyBqc29uX2ZpbGU6CiAgICAgICAgICAgICAgICBqc29uLmR1bXAobmV3X2RhdGEsIGpzb25fZmlsZSkKICAgICAgICBzdWNjZXNzX2xvZyA9IGYnc3VjY2Vzc2Z1bGx5IHdyb3RlIGFydGlmYWN0IGRldGFpbHMgdG8gdGhlIGFydGlmYWN0IEpTT04gZmlsZSBpbiBEQkZTIC0ge25hbWV9IDoge3BhdGh9JwogICAgICAgIG1scnVuX2xvZ2dlci5pbmZvKHN1Y2Nlc3NfbG9nKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyB1bmtub3duX2V4Y2VwdGlvbjoKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZidsb2cgbWxydW4gYXJ0aWZhY3QgZmFpbGVkIC0ge25hbWV9IDoge3BhdGh9LiBlcnJvcjoge3Vua25vd25fZXhjZXB0aW9ufScpCgoKCgppbXBvcnQgYXJncGFyc2UKaW1wb3J0IGpzb24KcGFyc2VyID0gYXJncGFyc2UuQXJndW1lbnRQYXJzZXIoKQpwYXJzZXIuYWRkX2FyZ3VtZW50KCdoYW5kbGVyX2FyZ3VtZW50cycpCmhhbmRsZXJfYXJndW1lbnRzID0gcGFyc2VyLnBhcnNlX2FyZ3MoKS5oYW5kbGVyX2FyZ3VtZW50cwpoYW5kbGVyX2FyZ3VtZW50cyA9IGpzb24ubG9hZHMoaGFuZGxlcl9hcmd1bWVudHMpCgoKZnJvbSBweXNwYXJrLnNxbCBpbXBvcnQgU3BhcmtTZXNzaW9uCmZyb20gcHlzcGFyay5zcWwuZnVuY3Rpb25zIGltcG9ydCBhdmcsIG1pbiwgbWF4CmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IGpzb24KaW1wb3J0IGZzc3BlYwoKZGVmIHByb2Nlc3NfZGF0YShkYXRhX3BhdGg6IHN0ciwgZGF0YV9vdXRwdXRfcGF0aDogc3RyKToKICAgIHNwYXJrID0gU3BhcmtTZXNzaW9uLmJ1aWxkZXIuYXBwTmFtZSgnTXVzaWNEZW1vJykuZ2V0T3JDcmVhdGUoKQogICAgc3BhcmtfZGYgPSBzcGFyay5yZWFkLnBhcnF1ZXQoZGF0YV9wYXRoLCBoZWFkZXI9VHJ1ZSkKICAgIHNwYXJrX2RmID0gc3BhcmtfZGYuZHJvcCgnbmFtZScsICdpZCcpCiAgICBtdXNpY19zdGF0cyA9IHNwYXJrX2RmLmdyb3VwQnkoJ2Zhdm9yaXRlX211c2ljX3R5cGUnKS5hZ2coYXZnKCdhZ2UnKS5hbGlhcygnYXZnX2FnZScpLCBtaW4oJ2FnZScpLmFsaWFzKCdtaW5fYWdlJyksIG1heCgnYWdlJykuYWxpYXMoJ21heF9hZ2UnKSkKICAgIG11c2ljX3N0YXRzLnNob3coKQogICAgcGFuZGFzX2RmID0gc3BhcmtfZGYudG9QYW5kYXMoKQogICAgcGFuZGFzX2RmLnRvX3BhcnF1ZXQoZGF0YV9vdXRwdXRfcGF0aCkKICAgIHJldHVybiB7J211c2ljX2RhdGEnOiBkYXRhX291dHB1dF9wYXRofQpyZXN1bHQgPSBwcm9jZXNzX2RhdGEoKipoYW5kbGVyX2FyZ3VtZW50cykKCgppZiByZXN1bHQ6CiAgICBpZiBpc2luc3RhbmNlKHJlc3VsdCwgZGljdCk6CiAgICAgICAgZm9yIGtleSwgcGF0aCBpbiByZXN1bHQuaXRlbXMoKToKICAgICAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KG5hbWU9a2V5LCBwYXRoPXBhdGgpCiAgICBlbGlmIGlzaW5zdGFuY2UocmVzdWx0LCAobGlzdCwgdHVwbGUsIHNldCkpOgogICAgICAgIGZvciBhcnRpZmFjdF9wYXRoIGluIHJlc3VsdDoKICAgICAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KHBhdGg9YXJ0aWZhY3RfcGF0aCkKICAgIGVsaWYgaXNpbnN0YW5jZShyZXN1bHQsIHN0cik6CiAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KHBhdGg9cmVzdWx0KQogICAgZWxzZToKICAgICAgICBtbHJ1bl9sb2dnZXIud2FybmluZyhmJ2NhbiBub3QgbG9nIGFydGlmYWN0cyB3aXRoIHRoZSByZXN1bHQgb2YgaGFuZGxlciBmdW5jdGlvbiAtIHJlc3VsdCBpbiB1bnN1cHBvcnRlZCB0eXBlLiB7dHlwZShyZXN1bHQpfScpCg==', 'original_handler': 'process_data', 'artifact_json_path': '/mlrun_databricks_runtime/artifacts_dictionaries/mlrun_artifact_a9c770f8377046bda3061e61a5c015c2.json'}
data_path=dbfs:///demos/mlrun_databricks_demo/1711553684480_33/music.parquet
data_output_path=/dbfs/demos/mlrun_databricks_demo/music_output_new.parquet
music_data
databricks_run_metadata
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:35:07,910 [info] Run execution finished: {'status': 'completed', 'name': 'process-data-process-data'}\n" + ] + } + ], + "source": [ + "for name, val in job_env.items():\n", + " process_data_function.spec.env.append({\"name\": name, \"value\": val})\n", + "params = {\n", + " \"task_parameters\": {\"timeout_minutes\": 15},\n", + " \"data_path\": dbfs_data_path,\n", + " \"data_output_path\": output_path.replace(\"dbfs://\", \"/dbfs\"),\n", + "}\n", + "run = process_data_function.run(\n", + " handler=\"process_data\",\n", + " params=params,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9a8db175-51f4-4218-afd1-752cc0e65216", + "metadata": { + "tags": [] + }, + "source": [ + "## Create an MLflow Xgboost function\n", + "\n", + "The following code demonstrates how to create a simple Xgboost model using MLflow and log the results.
\n", + "MLflow will log the model, parameters, metrics, and artifacts, and MLRun will track the run and collect the data." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "44a1e133-954d-47a3-9b0f-6e181fe12ea7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting training.py\n" + ] + } + ], + "source": [ + "%%writefile training.py\n", + "\n", + "import mlflow\n", + "import mlflow.xgboost\n", + "import xgboost as xgb\n", + "from mlflow import log_metric\n", + "from sklearn import datasets\n", + "from sklearn.metrics import accuracy_score, log_loss\n", + "from sklearn.model_selection import train_test_split\n", + "import pandas as pd\n", + "\n", + "def example_xgb_run(df: str):\n", + " df = pd.read_parquet(df)\n", + " \n", + " df = df.replace([\"f\", \"m\"], [0, 1])\n", + " df = df.replace([\"Pop\", \"Rock\", \"Classical\"], [0, 1, 2])\n", + " \n", + " # Prepare, train, and test data\n", + " y = df.pop('favorite_music_type')\n", + " X = df\n", + "\n", + " X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.2, random_state=42\n", + " )\n", + "\n", + " # Enable auto logging\n", + " mlflow.xgboost.autolog()\n", + "\n", + " dtrain = xgb.DMatrix(X_train, label=y_train)\n", + " dtest = xgb.DMatrix(X_test, label=y_test)\n", + "\n", + " with mlflow.start_run():\n", + " # Train model\n", + " params = {\n", + " \"objective\": \"multi:softprob\",\n", + " \"num_class\": 3,\n", + " \"learning_rate\": 0.3,\n", + " \"eval_metric\": \"mlogloss\",\n", + " \"colsample_bytree\": 1.0,\n", + " \"subsample\": 1.0,\n", + " \"seed\": 42,\n", + " }\n", + " model = xgb.train(params, dtrain, evals=[(dtrain, \"train\")])\n", + " \n", + " # Evaluate model\n", + " y_proba = model.predict(dtest)\n", + " y_pred = y_proba.argmax(axis=1)\n", + " loss = log_loss(y_test, y_proba)\n", + " acc = accuracy_score(y_test, y_pred)\n", + " \n", + " # Log metrics by hand\n", + " mlflow.log_metrics({\"log_loss\": loss, \"accuracy\": acc})" + ] + }, + { + "cell_type": "markdown", + "id": "1cf984c9-78a9-443f-9465-111263101dcd", + "metadata": {}, + "source": [ + "## Log the data from MLflow in MLRun " + ] + }, + { + "cell_type": "markdown", + "id": "365e4b39-9f39-40ae-aac4-7c4f42bce9bd", + "metadata": {}, + "source": [ + "### Change the MLRun configuration to use the tracker\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0b194d04-e08f-4161-a65b-4f18d10fdbf0", + "metadata": {}, + "outputs": [], + "source": [ + "import mlrun\n", + "\n", + "mlrun.mlconf.external_platform_tracking.enabled = True" + ] + }, + { + "cell_type": "markdown", + "id": "b16bb4db-8a2a-4453-a42e-0e8e74ab8f53", + "metadata": {}, + "source": [ + "These are the three options to run tracking:\n", + "- Set: `mlrun.mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime` to True. This determines the run id and is the safest method\n", + "- Set the experiment name at: `mlflow.environment_variables.MLFLOW_EXPERIMENT_NAME.set`. This determines the experiment mlrun will track and find the run added to it.\n", + "- Just run it, mlrun will look across all experiments and search for added run, this is not recomended." + ] + }, + { + "cell_type": "markdown", + "id": "8b7bc72a-bd1b-408a-afa8-e474d91c4a20", + "metadata": {}, + "source": [ + "### Create the mlrun function" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3382b909-a8dc-41a3-afb1-b64df9bb7318", + "metadata": {}, + "outputs": [], + "source": [ + "# Use the first run option from above\n", + "mlrun.mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime = True\n", + "\n", + "# Create a MLRun function using the example train file (all the functions must be located in it):\n", + "training_func = project.set_function(\n", + " func=\"training.py\",\n", + " name=\"example-xgb-run\",\n", + " kind=\"job\",\n", + " image=\"mlrun/mlrun\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "91597f57-364d-4d2a-b926-97b9d8afc81b", + "metadata": {}, + "source": [ + "### Run the function\n", + "\n", + "Run the function using MLRun. This will log the data from MLflow in MLRun.
\n", + "After running the function, you can look at the UI and see that all metrics and parameters are logged in MLRun." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5a726ca8-8057-41ed-be4e-35e5e0582de9", + "metadata": {}, + "outputs": [], + "source": [ + "import mlrun.feature_store as fstore\n", + "\n", + "feature_set = fstore.get_feature_set(\"music_fset\", \"mlflow-tracking-example\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4de1229a-cc59-4846-8473-3178e682efa6", + "metadata": {}, + "outputs": [], + "source": [ + "df = feature_set.to_dataframe()\n", + "df = df.drop(['id'], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8249a933-031c-4f2e-88c2-161dd4cfb7ed", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# df = project.list_().to_objects()[0].to_dataitem().as_df()\n", + "df_path = \"./music.parquet\"\n", + "df.to_parquet(df_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "8ba452dd-1756-4bfb-af64-d741e234dba3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:37:22,829 [info] Storing function: {'name': 'example-xgb-run-example-xgb-run', 'uid': '6ff324dd21d64b6290d45a001957dda2', 'db': 'http://mlrun-api:8080'}\n", + "> 2024-03-27 15:37:22,912 [warning] `mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime` is set to True but the MLFlow experiment name environment variable ('MLFLOW_EXPERIMENT_NAME') is set for using the name: 'example-xgb-run-example-xgb-run'. This name will be overriden with MLRun's runtime name as set in the MLRun configuration: 'example-xgb-run-example-xgb-run'.\n", + "[0]\ttrain-mlogloss:0.82467\n", + "[1]\ttrain-mlogloss:0.64706\n", + "[2]\ttrain-mlogloss:0.52480\n", + "[3]\ttrain-mlogloss:0.43768\n", + "[4]\ttrain-mlogloss:0.37410\n", + "[5]\ttrain-mlogloss:0.32686\n", + "[6]\ttrain-mlogloss:0.29057\n", + "[7]\ttrain-mlogloss:0.26192\n", + "[8]\ttrain-mlogloss:0.23885\n", + "[9]\ttrain-mlogloss:0.22004\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024/03/27 15:37:23 WARNING mlflow.utils.autologging_utils: MLflow autologging encountered a warning: \"/User/.pythonlibs/mlrun-base/lib/python3.9/site-packages/mlflow/types/utils.py:393: UserWarning: Hint: Inferred schema contains integer column(s). Integer columns in Python cannot represent missing values. If your input data contains missing values at inference time, it will be encoded as floats and will cause a schema enforcement error. The best way to avoid this problem is to infer the model schema based on a realistic data sample (training dataset) that includes missing values. Alternatively, you can declare integer columns as doubles (float64) whenever these columns may have missing values. See `Handling Integers With Missing Values `_ for more details.\"\n", + "2024/03/27 15:37:23 WARNING mlflow.utils.autologging_utils: MLflow autologging encountered a warning: \"/User/.pythonlibs/mlrun-base/lib/python3.9/site-packages/xgboost/core.py:160: UserWarning: [15:37:23] WARNING: /workspace/src/c_api/c_api.cc:1240: Saving into deprecated binary model format, please consider using `json` or `ubj`. Model format will default to JSON in XGBoost 2.2 if not specified.\"\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
mlflow-tracking-example-guy0Mar 27 15:37:22completedexample-xgb-run-example-xgb-run
v3io_user=zeevr
kind=local
owner=zeevr
host=jupyter-zeevr-9f4ffb7bb-8c4mf
mlflow-user=iguazio
mlflow-run-name=stately-cow-437
mlflow-run-id=f66d6149d54c4958a2485c941d86a538
mlflow-experiment-id=608717337209571124
df
colsample_bytree=1.0
custom_metric=None
early_stopping_rounds=None
eval_metric=mlogloss
learning_rate=0.3
maximize=None
num_boost_round=10
num_class=3
objective=multi:softprob
seed=42
subsample=1.0
verbose_eval=True
accuracy=0.7142857142857143
log_loss=0.9622776094122579
train-mlogloss=0.2200447738170624
feature_importance_weight_json
feature_importance_weight_png
model
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:37:31,415 [info] Run execution finished: {'status': 'completed', 'name': 'example-xgb-run-example-xgb-run'}\n" + ] + } + ], + "source": [ + "# Run the example code using mlrun\n", + "train_run = training_func.run(\n", + " local=True,\n", + " handler=\"example_xgb_run\",\n", + " inputs={\"df\": df_path},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "655d5c46-2c0a-46f2-bbec-a58853260476", + "metadata": {}, + "source": [ + "### Examine the results\n", + "\n", + "You can examine the results using the UI or by looking at the outputs of the run.
\n", + "The outputs include the model, the metrics, and the artifacts, and are completely independent of MLflow." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "d23beb02-e455-48dc-9d9f-9e3d4549ec71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'accuracy': 0.7142857142857143,\n", + " 'log_loss': 0.9622776094122579,\n", + " 'train-mlogloss': 0.2200447738170624,\n", + " 'feature_importance_weight_json': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_feature_importance_weight_json@6ff324dd21d64b6290d45a001957dda2',\n", + " 'feature_importance_weight_png': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_feature_importance_weight_png@6ff324dd21d64b6290d45a001957dda2',\n", + " 'model': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_model@6ff324dd21d64b6290d45a001957dda2'}" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_run.outputs" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "b05f4c2a-5f2d-4d7c-9c21-39c0a949cfc3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'accuracy': 0.7142857142857143,\n", + " 'log_loss': 0.9622776094122579,\n", + " 'train-mlogloss': 0.2200447738170624}" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_run.status.results" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "925b3445-18b4-4497-9783-52b4cd069401", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "train_run.artifact(\"feature_importance_weight_png\").show()" + ] + }, + { + "cell_type": "markdown", + "id": "227c4358-4c34-4d1c-acb4-e37ca110b8bf", + "metadata": {}, + "source": [ + "### You can also examine the results using the UI" + ] + }, + { + "cell_type": "markdown", + "id": "dde00fd1-a1f0-4c56-80c2-c5d36a9062a1", + "metadata": {}, + "source": [ + "Look at collected artifacts: " + ] + }, + { + "attachments": { + "95b9b198-55c9-4a67-b0bf-103c9ae0272e.png": { + "image/png": "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/LapWpuGkpN9+/WySy4/1+rWreOXpbk2qU8994A94I71/bcjffhO4bX2HdrauRecZv136eu306+99tnd/hw30ac41WI1JSW/vepxJxxhTZo0smefftlmu6pzCtVpdOna0c44+2QbsOuWOfwKfiGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAXOOb4wyqtRGpqqp125km+46Sea3Tr3tl3o1yxfGWh99WgYX07+7wRvgDYTj26+qJcKhQW7Fdo4xi+SLYkq5tSrVBVuKdWTLKv1y2IyVGWZW+0W5eMsfMb9rA9a23JdemYOnY8RpJr07l18b14nE2Uc+rDXrZ0uW87qgptRcdjrtXpyy+86Rffdtf1EROSa9dk2MaNG30Ft6L7h79W4E4htnr161p1F5AraWRlZflzUkW4lJTCfWw3b8701eAaupauNWpUL2kKliOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEApAh+9/7mtW7feb1G7dk078JB9S9l6+1r1xqvv2pTJ0wudVNdunezo4yom9Lc5L8dmbF7jzycjN8vOmPu1xTpIpvaoz7Tey9QyVaNDtToltkz1G2zDr/wjbMMEFbGrWqG2at3CV4QLP35GxjqbO2e+jfrp19BiBdMijfQ6tcsMwWm/5OQka96iaakhOG2nFGbzFs2KheC0rlq1qtbalTgkBCcNBgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACWyegYlbTp830P/VdS9HKNOo3qFfsdFURrqKGQmoNquQXBluYtT7mITi9L4XtZmdm+LeoY+mY8RrFy6nF60gJmPeu2x607775KXSk1q1bWtt2rUKveYIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKVV2DQbgOse48uvrlmep30SvVGBg/ZxTp2al/onBs3aVTodaJfNKlSw9bnZsf1sJkuDJeWXMV0rHiOHSoIFw6lCmw33HR5sapx4dvwHAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBCqXQJ1KFoALdNPS0nxXyeD19vLYIrWmTdy0Mm6no7aoOka8R1KeG/E+SKLmX7lilS1cuNjq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QAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEiJAEC4hzBwEAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgXgIE4eIly7wIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIJESAIlxBmDoIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBAvAYJw8ZJlXgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgYQIEIRLCDMHQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQiJcAQbh4yTIvAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAQgSqJOQoHAQBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQ2A4EJm5aZY8uH28j1y+2TXk528EZ7finkJaUYoNqNrVzG/aw7mn14vKGkzIyMvLiMnMFTpqxdr01a96kAs+AQyOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMD2JqAQ3ClzviAAV0EfjAJxz7cZFpcwHK1RK+hD5bAIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQWAFVgqMKXGLNw48me30G8RgE4eKhypwIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCw3QmoHSqjYgXi9RkQhKvYz5WjI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQIIEqAaXIOhSDhOvz4AgXCnorEIAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEENj+BQjCbf+fEWeIAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQigBBuFJwWIUAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIILD9CxCE2/4/I84QAQQQQAABBBBAAAEEEEAA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AggggAACCCCAAAIIIIAAAggggEB8BAjCxceVWRFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIkQBAuQdAcBgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAID4CBOHi48qsCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCRIgCJcgaA6DAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQHwGCcPFxZVYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEECRCESxA0h0EAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEIiPQJX4TFvxsy5buqLiT4IzQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQiLvADhuEa9S4QdzxOAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEDFC9AateI/A84AAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEBgGwRiWhFu/uK1Ns/9lHcM6tOyvLuwPQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJeIGZBuJFj59so97M1o1XTdGvpfhgIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIlFcgZkG44MADXXU3BduiGaoep/CcHgnCRSPGNggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAkUFYh6EK6m6m9qmfvrDDB94239Ih9B5jAo94wkCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC5RdILv8uW7fHhOnLbO26zTbRPSoUx0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgFgIJC8Ip/JZeq5o/55GuHSoDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgVgIJCQI9/vExb4aXL/uzax7x0axOG/mQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMALVIm1w+c/zrTU1BSrVjXFjj2gu59+6cr11qh+Tevbval9+sMM3xr1hff+tKysnFgfnvkQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQT+YgIxrwhXJz3Nhd5q+LCbQm8ay1Zu8Mv0fP8hHXxVOG2jbRkIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIbItAzCvC7dKzubVsmu5boerE1BZ1masIt1NYS1SF4TTmL15rcxas9s/5hQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggMDWCMQ8CBd+EhOnL7P5tdZaeq1qvi1q+DqeI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBALgZi3Rg1OalCflv7p2nWbfYW4YDmPCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCMRSIG5BOLVH7e7aoaoaXHhb1FiePHMhgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggEPPWqPMWrw2pKgAXhODmhy0PNgjfNljGIwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALlEYhZEK6VqwA3yh151Nj5/rE8J8G2CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCGytQMyCcGqFOrBPy3KfhwJ02peBAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAwNYIxCwIp4MP2oog3NacNPsggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggEAgkB094RAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKAyChCE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QAABBBBAAAEEEEAAAQQQQAABBBBAAIFsIEBFuGzwELkFBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCAnC4RFREScym4AEeGHpGKl8tnttpJ1PwcOipw4ke0ebbIM6IwAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQE4QyJ1bJFeuMMmfTyR/3pxwx3HvkalR45pkiy0agitdIixb3As3gQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgkLHDkqcvSY6WNqZ2kgLqc1pkbNaU+c+0UAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFsJ1CwgEg+UxbtWGS2u7Uk3RBBuCQx0QkBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyNoCGoY7dcqUhMuBjSBcDnzo3DICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghkT4ETJ7PnfSV2VwThEhNiPwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQJYWIAiXpR8PF4cAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJCYAEG4xITYjwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkKUFCMJl6cfDxSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCQmQBAuMSH2I4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIZGkBgnBZ+vFwcQgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAokJEIRLTIj9CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACWVqAIFyWfjxcHAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQGICeRLrkJz9cxZuDtm9Q4sqdvvm7eGyyfwFN7c/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AAEEAiFAEC4QyuwDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAbwIE4fxGy4YRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQCIUAQLhDK7AMBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBvAgTh/EbLhhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAIhQBAuEMrsAwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwG8CBOH8RsuGEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEAiFAEC4QyuwDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAbwIE4fxGy4YRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQCIUAQLhDK7AMBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBvAgTh/EbLhhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAIhQBAuEMrsAwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwG8CBOH8RsuGEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEAiFAEC4QyuwDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAbwIE4fxGy4YRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQCIUAQLhDK7AMBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBvAgTh/EbLhhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAIhQBAuEMrsAwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwG8CBOH8RsuGEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEAiFAEC4QyuwDAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDAbwIE4fxGy4YRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQCIUAQLhDK7AMBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBvAgTh/EbLhhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAIhEByInbAPBPwtcO5cmJw5fUby5svj710lefu7d+6RLZu3S516NVPF8Sb5hJO4gfDwcHcLQUFB7jADySvgj+ui25w3d7EcP3ZC2nVoIdmyZXVPctaMf+S3X/+S/fsOSa5cOeT7QR9JlqxZ3PkMJE3A83o6W0pJz7evP/9RLl28JCVLl5DuN3Z2DjHd3nter5R0ndLtBeHEEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgRQgQhEsRl4GDuBIBDQLMmDZXVi1fI3v37LebyJ4ju1SrXknadmglhQoXuJLN+nWdsLAw+ezj7+w+Fi/8V555sa9f95faN65eLz3zlj2NggXzy3OvPJ6sp3Tp0iUJCztvjyFz5kySIUP6LKqpBtd1ulMuXrxoLUaP+SHRoc4zZ85K+KVwCcoQ5AbeVixfK/1f+chuc9euvdLnsXvs8JRJs+T9d7+yw3pz8mSIRMUj3clJGtBzunD+gt1G9hzZkrSt1LbytMmzZPKEGbEOO0uWzFK0WBHp2KWtVKpcPtb8QE7YtmWH3Z2+JjhNA9D6e0CDYHqs6ak9+/hr9nSzmrDom++9mJ5OnXNFAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE4hQgCBcnDTNSusCY0RPkn7mLoh3m6dDT8u+SlbJ+3SZ55oX/SU5TOSolNQ1OBWcKtoGbrFmjql2lpGNMScfiUQxOLnmOJNNBLl64TEb/Ntbu/aZbuslVTRsk05Ek727nzFrghuD0SCZNnCm33NY9UQfV68aHJNQ8X3OY8OrYCUPsup4V4HLmjHruDh/2h7vtJk3rS63a1USDiL5sb7/xqa1Gp9sc8fvXUqRIIV9uPkVvK66nlgbNdmzfJQO/Giwp8fH+1msfyVkTqEzPYbBwE86lIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghECPg0CLd7/0nZZX4S25rWLZnYVVg+nQvMNwE4JwSnwbKu3TpKrty5ZMG8xbJp41bRQNzXn/8gTz3/mGTMmDHFaAUHB8uLrz0pu3bsSfYKSykGhQNJdQJ/jZkc7Zh1PLFBuGgbiBypWq2iDBryiRw7dlzq1K3hLrJv70E7rOG3t9553laRc2f6YSCuYJgfdpXiNlmvQW2pWr2ynAo5ZULFK9xqm3/8Pk4aXVUv3VZBTHEXigNCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEIgl4LMg3IIVu2Wh+bmSVqpobilpfmgIJFRg1vR57qJ9n3xIihUvYsdrmkpRn3zwtezbe0AOHTwiO3fstlWnZk6da+df3aKJ1G9Y2w6vXf2fzJg6xw43bd5YGjaua7vZW7Jouawz87Zt2yl58+Y2Xa1WlvbXtnYDdUN+/EVOngiRkqVLiFatWrRgqZQuU0JOHA+x2ypbrrR0vaGTHT5+/IQMHfSbHS5VpqR0v7GzDPnhF7ufDf9tlhtu6mLn6c2mDVtk+bLVstFMz2QCc2XKlZIu12vAL6etyjT2j4l22fadWpugSiVbVe7bL3+y27r5thukSNFCcuTwURnx8yi7nHNOdiQN3Rw9ckyGDxlpz+jqFlfJ3t37Ze3q9XL23DlR+x43d5VcuXJKiAnyDP5+hF1Or/n2bbtk86atkj17NqlZu7p07NzGDfV8/83PtrKUGqqltj2794mGf7Q1u6ax7Ny+W9asWm/H9WaK6UpyiakQ99gTD9hpq1asldkz5sthcw0ymXCmPibbdWwlZcqWctdJCwNHDh+TdWs3RjuVgwcOG9ttUrFSOXf6ksUrZMhPv9vxHjd1lrFjppjH8W554qkH5XdTVU+rwWnT+8ceeVGu79bBrj/go4iug7t0bSfFihWWHwaOcKvPafel/3v0JVsR7qFH7rTrT5syR6ZPmycbTQBWnzeVq1aQ++6/xTwWortr96oTxs+wx5DLVIqs16CW9H7gNvsc7vvYy/b47QbNzcsvvCd1TRDvsX732a5wR5nHwYzp80XPU18TatepLnffe3OK7H7ZOYcrvS9XoYz7GnlNq6byynNvi1aG026BDx08bF5nCttNX+71ytm3PocmT5huw3TnTbez2r2xvgarvXZn6jxndHl9zdTnrzZ93um6GTJmkEf79bbTPG+c56ZWg9Om918MGCjOa552navPzy3mMXnKPL7y5csjterUkBatm6W7LlTVZ/3aDbLSdDus10yD46XN76LOXdtLvvx5dbbblv+7WubNXmBfwwoVKmCu1VVmfKGd365jS6lWo4oddq5bWn+tc2EYQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBVCPgsyCcc8ZNTHU3DbYlpGn1OA3P6T1BuISIsYwKnDdhmOPHTlgMDWU4ITidoOGKFq2vlt+GR3SlqOEl/fBeA3F2vqkk5wThNMTkTO/aPSK4pgGMhf8stcvqjVaW27tnv6xauVaeNl2tatemGvjR4IWzri4XagIeB/YftIGR3bv2ShcT6tFl163Z4C5XxVS70qZdDWo7f/68vdeblcvXyLDBEaEhZ6KGDFaacNWzL/aVvCbI4exv7Zr/bBBOK98521q1Yo1oQG7r5u3ucm3at3A2labuz549557jzqER19U5QQ2q6WOj39MPS5gJ7zhmzr0udyok1AYg9+3dL/c9eIdddbOx1KCPBmicFnLylLt+pcrlZYux9Zyvw874UhP6ch5zzvonjp+U/0wXvXf3vlVq1qrmTE719xPGT3fPodet3eS3X/6y4xp0e/KZh9x5B/Yfsl0U64S33/jMnX7k6DF3ujNRuzLWcFmhwgXdeTVrVZWM5jmk8zybjuvzXNtH738jE03gybMdOnRE5s9dLD8OHmCCVRFhuPff+VKmTJ7tLqbXbY95Xs804bbhv34VK9i3betO+/wNN6XhnnnyDVljgrFO0+Cerjt92lz5aehnUtSEJ9NqU+ccJuyrQThtmTNntvfxvV5pwEpDuZ9++I1d3rnZaex2Dh1lQqW7pLsJAesyznNTw8VO27J5mw0dOuMx77VanbOeM0/Hy1csa5/fH77zhX2NdubtM6/XGo7W437i2UfcULMzPy3fL5i/xA30OuepYeIVJnTdp9/9Uq58GTt5zsx/ZNyYSc4issNcK+f3i07UdbSlp9c6e8LcIIAAAggggAACCCCAAAIIIIAAAggggAACCCCA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CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEB8AgTh4hNiPgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQIoWIAiXoi8PB4cAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBCfAEG4+ISYjwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkKIFCMKl6MvDwSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCMQnQBAuPiHmI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIpGgBgnAp+vJwcAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAvEJEISLT4j5CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACKVqAIFyKvjwcHAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQHwCBOHiE2I+AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAihYITtFHx8GlSoGTp1LlYXPQCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAulIoEDedHSy6eBUCcKlg4sc6FMsVzIo0LtkfwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIJCOBegaNR1ffE4dAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgLAgTh0sJV5BwQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgXQsQBAuHV98Th0BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSAsCBOHSwlXkHBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBdCxAEC4dX3xOHQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBICwIE4dLCVeQcEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIF0LEAQLh1ffE4dAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgLAgTh0sJV5BwQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgXQsQBAuHV98Th0BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSAsCBOHSwlXkHBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBdCxAEC4dX3xOHQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBICwIE4dLCVeQcEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIF0LEAQLh1ffE4dAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgLAgTh0sJV5BwQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgXQsQBAuHV98Th0BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSAsCwWnhJDgHBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQT8IbBl8zaZOnGW6H1i2oefvZGYxVkWAQSSKEAQLomArI4AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQdgWuJASXnBrP9HvVJ7snyOcTRjYSQAGCcAHEZlcIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACqUvAqQRHMCyw180J9OEeWPfUvLcMqfngOXYE0oLAlLknZd2ms8lyKus3n5VJs08my77T4k6T81rOW3pKlq4+nSpY/9tyVoaMPior1p1JFcfLQaYPgUNHLsj4mSflRMjF9HHCnCUCaUBg2ZKVMnfWgmhncuHCBVmyaLkMGjhM/hg5Tv5dskJOhYRGWyY1j1y8eFHGj50iW7dsT82nEfBj9/ZYCfhB+GCH+/cdkN9G/CmTxk/zwdbS9yZCQk7Z59KB/QeTBWL+nIX29SlZdp7Gdrp92057Lc+fPx/wM3Nek7dt2RHwfQdqh+fPX5CF85fIT98Pl317DwRqt+wHgXgFeB2Nl4gFEEAAAQTSscD6dRv5uzEdX39OHQEEEEAAAQREqAjHowCBZBYY+scxadYgh1SvlDXgRzJ/aajMWBAinVrmDvi+0+IOk/Na/v73ccmbO6M0rJU9RdN+O/ywzFl8SrJnyyAF8wdL3erZkny8Fy+FS4agIDH/kqV9PviQbNsVJp+8UiJZ9h/fTpPbJ77j89f86fND5Oc/j8qAl0tIgbzx/3dn665zMnzMUalUNovkyZXR62GFh4tcMjcZM8T/YBs54bhMnnNSvnqjlGTJHP/yXnfIRL8LzJo+T0b8PFJ279orjZs2kNZtr5FrWjWVDBkyyCXz2nLf7Y9Kq7bN5Z7etyXpWMLN40a/sdSxcxtp36m13da82QtlxNBR8uXAD+z+vC2TpJ16WfnMmbNyV6+H5c57e8n1N1zrZYnETdJjvnTpkmTM6P05E9/Wfvj2Zzlz+qz878kH41vU6/xxYybJzh277DVzFuhz/1OyccMWqVCxrOj57t2zX559qZ907treWcSv9xqK0MdPkJ9+KZ0POy8fvvO5PPb4A1K+QtlY57Jrx27p+/Dz8vyrj8tVTRvGmp9eJ3h7rMRnoY/vmdPmyorlq2Xjf5vli+8+kEyZMsVaLeZzOdYCCZigrzfh4VHPJW+vPwvmLZYXnn5DsmfPJh27tJVOXdolYMuXX0SfvyJB5jGb+N9TGiB70RzPq289J2XKlrr8jlLg3MMHj9jn0jsfvSpFihYO+BEOHfybFChYQBo0qhvwfae1Ha5avlYGfPCV/V3g7Tnqz/M9dy7MPo76PvWQlKtQxp+7StS2Y74uTZ00U0b+Msb+f6d02ZJy5z295OoWTWJt09v/RR6+93HZsnm7VK5Swa5TTIrEWi8xE2K+3iVmXV8s6+311Rfb9dU2ktvHV+dxJdv57ONvZdOGrfb/xglZPyGvo4n5f9lLz74l2bJllZdffzohu/f5Msm9f5+fEBtEIMACoaGnRX//Tfx7qvn7r68UL1HMPQINdf86bJSZN83+/dmqXXNp26Gl1KxVzV3Gc+CXoaNl2uSZ7qQsWbNK0+aNpE27FlKiZNR23QUYQMAPAvplrq8++97rlu++/1Zp0fpqr/OcifNmL5DhQ0Ym+u/GhP4d7OzH8/5yz0NdLqH/J9Vl43ve6vsi/5mwn7emz+/b7uoZa9bK5Wvkc/P/jZjt0X4PSP1Gdezk1196377P5LlMcfO8f/O9lzwnxRr+YsBAWbtmvXz+7fuSOXPmaPNfef5t2bt7nzzxbB+pWbt6tHk6ol+qeeu1DyVHzhx2/VgLMAEBBFK8gFaIi6+7VP1MoMO1EZ8LpPgT4gARSCMC8X8ynEZOlNNAAAEEEEheAQ1kafiyXo3s8syDvvnAMST0ojz04i7pdV0+6dY+T7KcYLlSmW2wL1l2Hs9OU4JPPIfot9lFCgZLtQpZJVsW3xW/HfjLYZm3JFSGfhL/B50limSSKuWzSqbgxIcL/IbChqMJaEWt/i+9Z8NC3W7sIqtXrpXXXnxX9MPsHj272mBIvYa1pbQPAh6rV66TpYuXyzMv9nWPQauVVaxczoamdKK3ZdyFfTQQHBxsQxe+evN+0T9L5fmnXpdBw7/0Gsq63GFrSG3Y4N/l1TefvdxiiZqn11RDcM+/8rh9s/fI4aNy43V3JWobSVlYK0x1bX+LfXOzW4/OSdnUFa+rb5w2aFxXChYqeMXbYEWRs2fPydv9P7IVB5tc3UiaXt3Yfa7G9In5XI45PyHj338zWP4aPUEmzBhpF9dgWszXnykTZ5oP9IrKz799K/pc9kV7+N4nRN/U7//284ne3BQTrNHncekyJRO9LisggIB/BTxflxYtWGpezz62AfF7Hrhdxv05UTRw882PH0u1GlWiHUjM/4voh3Iagnup/1PiBPmjrXAFIzFf765gE0laxdvra5I26OOVk9vHx6eTqM1VqFhOcuTw3Rf7Evv/surm+ZAlS/QPrhN1AklcOLn3n8TDZ3UEkk1AQ+mffPB1tMpXFy5Er/Q/cdxU+fG7YXJ9j2tNsLuiTJk4Q/4c+bcMHPypVK5aMdaxHzly1P7+e/ixe+28nebLRr/8PEp+/HaojBj9fbSQXayVmYCAjwQ0VKb/D7uh53VSpEihaFstUbJ4tHFfjSTm72DPfSbkeZiY/5PqtuN73jZr3jjW8/fAvoP2C6c9bu7qeXjusH4JVk173trdfB0squXJF/WZgh5n2fJlTFC2qrtAvvz53GFvAyeOn5TRv4+1s7SScsyQ4q4de0SrWI8xf/N7C8JNMAFePS790hsNAQRSp0B8ITg9Kw0Da0vOMNyVdinqdElqT4AbBFKRgG/eQU9FJ8yhpg8B7dru6ImLUrJoJjeEEHr6koSdD7dVs5wiHafMtPMxpp09d0n27D8vpYtnNlUfov5LrNPPhYXbSkEHDl+QM2cv2WX+z95ZwFlRtWH80N0tbetnK5goIqkiYFIqLQhIh5R0SEhIi9KKoBiUYqCoYHeLIN3d+b3Pe/fcnTs7t3bvLrvwnB/svXfmzIn/iZk588z7itEPDZu3nTBZs6Y3+fPEW0U5eeqMOXDwtOaJMu3cfdKUlHQjsQ60ZfsJsQxhTLHCmYJaukKaqEvunPF57t0PSyTx2yA+2rD5hMkmZYMwBPuzZkkn/wPFIagT6liiWKaQ1o72CFdY8zomcXfuOWkuLJXF7D94So/JkT0+TWc+Tna79wq7Y2cMRCKRBC8OkdQbadt+UKZ45gCGzvJE2pY45ri0fy5hvX3XCWF1xpQslllZh6oH2nCtWJrKmydjQN9wHxNJnSJpS8TZuEX6orQv2jtUcLdl2ZJZtH+gfW0fRXr7D5w2OXOk17EEYRWsryHOJunzOaXNYYkuXMDYAQekd3HpzAZ553OMFezfuOW49nek7Q5ebYk2QToIB6VciANLXpH0R7Qfxs1/m49ru1gLYJG2l7N8lW7NpfWy2yxXvEGHuaSQWL7LHWdhDGXGXFGmRJaAvuM8Bu1XWNrOOa5t2ijfeikzGCFdZ7Bp2LF5gYwxLz72GC+m2OccH+HGK8Yn5pZgfcBr/Nr83Z+nZL7cL/NlrpzpTcYMvrkXZTlxUsZdjvg+hjplyxo4h3nlc0nZrOap+pm1fM68tu30jV/MdUeOClAJ6MfOgPMF4mFM2PMF5jTM++jDlrUdJ85j7fcbrsqmlj7tOcJuR/88eOi0Qft4BexD3iVkfnGmH027eKV7Pm47KgKNo8dk/s0bv6AFt5l4KJYrV055I/xNXWQaMKyHvq153wNVzU8//GpgJQ5COIRGTesneCCGsY2FcDyoK1ioQABaLP6dFNdsECM5A9wYlrvlBrE65Fu83Lxpi/numx9Ns5aP+6O54+zZs1fzgGWQTbJYV1iORbltQP2QX67cOc36dRtN/gL5TO48uXQ33MOtl4W2wkUKBhyTKVNG07JtY7leCbRCizz+k0W5vLL4ly9/XpuF/xNWo5DHBSWK+t9shbvR/fsPaBws+mGR1vnwEpwP7D+owh1/Qo4vK1d8ofxvq3CzbkV8WFLLmjWLvimbWR5EWitNsOaBhcOiFxTR/Y5k/F8PHz5i8HYvQumypbSd/TtdX4K1IeokdrkCmKEdwN0Kjyz3PHkDreliwXjv7r2ak7LZd0DbQ+slE0lGsSQG63VlpGzWYhEsaqEsRYt5W9dB+24Ri3YQY4ay2AUx0tGjR7WcKFfr9s1Mzpy+vmK5YkEV1vFy5MweMCYsmv1S3h07dpkSJX0L6keOHPGMZ+PjE+XDQnIxaRe7YIt+cPz4cT3WWsU7dPCQXIOfCNgGXpvkjehSpYv7eSBNvPV98OBBkydPbu1T27fuMCVKFfePw71795lDBw8HWGKw4zpPnjzS5w6YnaiH4xikGyyg3rt37THFpd4YHza8/eYSFcENGt7b3B7XR+0+56fXWMb+YPWz/cc5bk+LJbjDh44Y9GH0t+zZs2t97fyD8blv3z7zw3c/qzgObYr2teXV8bF+k1gXyx/Qd53lRD0PSjtASIdgmR0XC4MoK/IFc4xBBPTL/9ZtkLYt6mevO+L+YE54582lOlfadrb74TrR9mv0W8Tdt2+/zjuwtIOAMmP+yCZzUda4bdgebEzYfhzN/IBjYK0SooYNwidbtmz+ORh5BQte/dodN9I6aT+Im78xRlDn3LlzBVjRBAuMI8zL9hzhzg+/bZvh+B3bd8r1STqZ9/P7x4ttO5T/8OHDJl8+31xu+xT2bxQOED5GIvTwOi9EWu9QfRLlwblCz20yBxQqXECZoI7ghTmvdFnMefHXZsnZlpZXTjlv27k5flv8OItkjrTtXUjOvWinYMFrHsgm/cPOfbbuthzRtqV7XoLIFmN/6sxxei6p8/B9pla1+mbR2+8lEMI5r0UwJ8EiJkLJUiX810+2XsHmT7vfazxjDnfPd5kzZzLYbuc+HG/nCDvX6RwQ5FwKTu5zkS1DsE87v2I/2gNzIa6htm/bqeMHAl/bDjh3Yu5yXh/Z8RjJeSfYNZlNwzmms8g1kJuPc7x6MUUd7BwZ7lyPMey+nsPxNkTLMrHXTcHyubtyBT1n2PLg084n6B+4rsU1JzjhfGAD5pMtmwOvcTAeva7L7DFen14vMYQ7H2J/sPuCaOdfr/xRTrhFxznMfe1p64B+e0yuA3EtY/st9kXaL2w6/CSBtErgmNzzr1v7n2nfpZVeg44ZEWjtCfMI3HtDNNOxa2utZvlbbjSP1mpkVolgxUsIZ1nUbfiQ/aqW4ru2f858uepbU+fh+/3b+YUEkptAtRr3mMuvvCRkNu57MHfkbXJfjTUD3O+Hsugf6X2wO/1w4xDxI7kmxb051rYiGbde1o2njJ+uaxP3VK2oRcT9F66P7HoOXpbE79btmul+9x+sR+AauMb9lU3N2tXdu4P+/vjDlbqvpKxDLH5neQIhnD3wg/dWmDYdmgesteCaZbFclzOQAAmkbQKwCIcQTGhmhWSpQQyXtkmz9CQQHYH41f7ojosq9sat+0WQFPiwKKoEGJkEIiRw+MhpM/DFrWbdxuN6BNzXPdOokCl3bXaz4suD6vau0cP5TdUKuVUs07r3RhXjvNCnuJFnUGbqq7vMyq8P+nOD5arOzQurEGLmm7vNyq8OiVAno9ko4hYECOXaPF7ITJ23U4UN2FamRGbTo3VRFVZ8+/NhM+aVHSL8yWL++e8Ydmto1bCgqVAu/mG23Y7PtVL2IRO2+tODgKZ326KewrHxs3aaX/48YoZ1v0BFWT/9ccQMnbjN3HVzThGAFDRrJM9B47epsAVpo2xgA8tZsKBlQ7dhm0Us52MGAVWfZ4pqXLvffoJv6z4bTKECGc2OXSdV/AbLSF2HbtbyoZw2PN17g6l4S07Tol5BM3vhHvOJ8L/0wizm93+OahSk0bVFEVNcxIpeIRSHcPWGCHL45G3mv02+OqGdakud61TzPRBKTFvaOkCgBNENAsRIqHNpEdp5BbidnfHGbhVbYv/lYp2q61OFE4gQsS9cnSJpy81SrubdN/jbG6JLlM8t8kF+Xm05rl8Jbd+6NfOZByr7hCPrxOVn71FbTFsZR7den8P0HL7FpJPnUhCu7RfhGQLq1bN1EbmRjheO6g7Hn0++PGCmL9itW+AyEv8nDy6pAqfJc3dq/7DR76+Ux9Sv5eufodoSaSxdsV8PW/zxfrPskwNqqSuS/oi2yS/uMiH0stbkomkvW1Z8jpq2XfvEhAElDcS1GCPoE7b/IU6l23KpuOv9lb7yok82kDpiLrJtYccn4iNAYNesbrzIZvlnB8xscd0J8S4CxlD3lkVUPGjTcI7Nqnfm8uQTiinStX091HjFXDP6ZVlEEaEaAuY4zK0QxiKEGr8awePPdplTOg3aZJ54ML/fXfPQSdvU7ewrz5eWRX2fCLC7zFdNHy1g7rk9V8h8ln2y38xbtMdMGlhShYgQoA0ct9U/f2P8ZhRrbQXyZjCDOvuEHyjWa+/uMX+s8c1TmA+b1ytgyl+T3WBOswFtXO+BfKbmPfECK7vPfi6Q/rlE+uec0WX0HII5E33F9gvM7Y/em1f7Bo5B3xkk5y+7H9seqpHXPFTdN29F0i44hiGeAKwVjRo23sxZMNUvmnlvyYfiwmycmTVvkmn1TBP9b10W4EEWFgSdD5DqPdhUF79aPdNUE14ui1YTRr+kohFsuPKqy8WiWRe/kKlnlwFmzd9rzcKls/0FgQhoibyB7rS4BMtOWCCzlljccbDwVqdGQ7VWh7dRbWjw5COmeasn9SfcQcHNBVyVwQoaFvVr1qluJo6dZl4X92c2QODXtuNT+sAQi4m1qzfQNJAWwkfi+nHcyMn+OsEVbPde7f3ilIULFpmpE2boYiAWDCtXr2hatGpkenUbqMIcpNGhdQ9TuVpFdSUFgcbz4qLi69XfYZcKv55oUtdUqnKn/rZ/3lm4TN072geZndr01F179uxTkQd+3FT+enN/rWrm+UFjNH9sw5u7XouW40dPlQXH9xHFtGrSUbkMGfmc/nb+CdWGE8dNM1+s/Mrffngjt2nDNqbzs221HEhnpPSpX3/+XSwBvORMVlzOLVQrA9g4deIMaZvX1bpXl3a9VbiFBVe06ytzxss8fkKtEeLhOgLcuKKNrrvhav2Ndho59EXtN9hQqHBBedhyn6n3+MO63/kHYrI2zbvow9Fx4mYX7h4bi0tfuHvEwx7knz59BnmwfljfQMaxaOPe/btof8fD48kvvmzmzV3oT/aGm65VoeaK1Yv829xfJo9/xcBlkA0tnn5SXY9ASAoXJdZ6EB7CPlarsYj5SpjxU0fowyksTrv7KBhD2PPdNz+Ybh36qvu9zz9dbZNXy4FYNP5C3IMigFm33h20nSEqxUMpLIQ7jxk4rJe5465b/Gk4v6At4PLEji/07X5DephyN1+vYrwZL83Vhw3lRcCKhwYQ6zjnBpuWeyxD2BCqfu5x275zKzN6xESbnI5729/s/FNbHrThO8JHyz/V/0Olb8NSHRbQYOnJBghuu8n4LSiiOIQ9Is7s3X2w+eWn3/Q3HqDAJWJOEXe2a9Vdt+HNdMw3M16bqC5OX5v9hpk3Z6F/TsDYbte5ZYDIDqI8jPUqMh/YsO7f9ZLXIBWdYRtEn4NH9FaxVutmnbW/wXokxEYQImOc2HrA9SwsdAYbE4mZH9D3T586LeXc5a8L3AX3Gdg1QLRry4/PYP3aGQffIZoJVyfMrf17DfMfivGI/jvx5VHmiisv1e1r16wzdes0DZjz+g3unkBMjci2n8OCANoTrrwrV7tLLXtNnztBrBaU0jSXvPu+wQPgRcvnmfQZ0mvbYsx/JQ9t0e/R1+EmqGGjRzW+159g5wVc0ISrd6g+iXMd+hr6qT1HIH+UBQ+npsu4Q4Dw6+l2Tf0WyJKzLSG8a1T/aYNzpbXainP5b7/8odYXITaMZI78aPlKcbM0WcuPP/Vlvm4u8yLmNXdwzwM4fxcomC9mbemelzDGb5K5zQqqMQZhOXStjFlncF+LOM+rLZv45tspM3zn42DzJ9ILNp6vvvZKvQaxeaIvYL6rcNet2i86dmvtd9u+bu0GPf/aOcLrXApXtJGOWZun/bTzK67vZr78mljnWCxz/qU6zhAHfbCHWMGD6ywIaRF0/pBrPrzsYMdjqPMOritDXZPZNOyYrljpDrPio880L/yxfHAdFIypvW4Id65HesGu53Lm8r28kRiWibluCpXPCLlGx0sD02a/iCKrZZTuHfv650i0Ac7baLfH6tfROHhZpIlc96CfI9hrnGDXZRopyJ8+zw7Wc/2IsQM0RrjzYahrSnstH838684fdYX1KVznI+D6DPcvl1x6kf7GdWpfsWZt+yj6Lc5x1994je6PpF9oRP4hgTROAILiSS+/oLX45KPPE9QG9/iTxfJbjhzxlpbsOdF+JjjIY4N9mSyUiMjjMG4igWQlEOwezOkauPMzvdVDAQqCtQWINHCv5g4QgUV6H+w+Ntw4RPxw16S/yDoL1jZwTY5r82jHLQT6c2ctMI2a1df1L7yg90DVenpPgfUJhJ07fS//4Tptr6w94WVQ5/X6XnlpB6FQoQL6Yl4GWYx2vrilOz3+wOJyhYq3mltuK6frIbhXBmtnsOssy2U9EOtaNuAlUVw34B7p159+t5v5SQIkcA4TSE1iOLdLV1jpvlDWXM+m1bpzuOlZtbNAQB4rJ2+Yv+w3g/8vTF9tVv2wMXkzY+rnPYFB47eqlSqIr3qKGA2ipbHTd6io7N6KudUa26w39+hviHJg2adDs8Iq6PpARCYQwUEs9ULvEvr5/a+HzXe/HPZzRXyIi4Z0vcA8+VB+FaS88PJ2c9Wl2cyAjsXMIyJqgNDsy+8P+Y/BlwNi4ee5dkVVvAHxxcTZO9V6VUAk+QELTQPGyhvHYmGpe6sipvUThdQC0RjJwyu0fryglh3pwfrcBPlE+iibXM+b4VO3axlRLpTPbQXOpplHLDBh/8MiuoDIZtRL3vnZ+BB0QDzUrWVhuynsJ9idFMtO/ToUMy0bFFQh3cerD3geF45DqHojwZde26liko7StiN6FDdXXpxVhVewZmZDYtpSjxErUgM7FTMQa0F8BGGLV/jlr6NSjl3SN7Iq20fvy6fimgVLfTdU7mNC1SnStkR5Kt+RS/OD0BLixtfe2ePOKuB3YtoSx9xyXQ4zqMsFKuSCaOjXv33CoYDEHT/uLJ9TBaLYhH425rkSItDLYF5f7BNJYtyBazkRHC36aJ+B0AohVFtCIIRjEGrI+B79XHH9HumffGLJDgK/22/MYaJtr3B5QJTYSfofxj1EcR99ccCgv0OYi7kJFtReXxzYF2C5sUOTwiKWLKLWwiDM+0bEtAg//Cbiifm7TCmxEgahKuLAQtmQCbC6El8aZ3sG4xOKqU0p1HhFHsMnb1fLbagjxMKo7+C4soQbvzYP9yesX8LyIOqKAJHdX/8e0znJCtN+/tPXz268OnvU8yVEqRAxoy9ivsOnFXM6y7Jx63HlizkYnucWLtsrD+/TmbHSZ2+SfBHQf6vIWIsmTJV5Cdb+IJRG/hCovjRPHs7HWTUcAJGe7Mf4GNmzuLn9phzmDZkvVn0Xfz4J1S7RlOV8iXvn3bdpVT/7ZJW/yitkYRxCkJJi6QMPi/B/pwiUsOA2btQUfZAU7O1uWBsb9NwIEa9dqgvtYyYOVcs1EG9ATIQASw6Nxe2YM3zysbV8Vl43Q+SEN2EfqFPDv+DmjmOPh7vW4WMGGOSFh50Qvn379Q92t37CKh0eFN92581mrrhrgcAIDwdnz5+iLkIhDpsxzScuCDhQfqDeEGrcekc5feiIt+ghZFowz+fSAQ/gIKi4Qx5ST505Rl1+wgrUlInTTb/Bz6rgBmmijBDKIEweP10X716cOlzTxMJi/97P+4UoiLNBLOpByFH93nvw0x/woA/8pr86wTwqC4NwJwu+T8vbungoiofB86V+sHzhDi3bNjFt2jfXzRNeGmGel8VddwjXhjffeqOWE+VD+OKzL/Vz5QpfH8Ji6Tdffq+CK93h+PPQY7VU5IZNTZ9qaGa9Hi+IwAPKB6RvjBo3SEWTcEkH6zKo07RZ41Qg9JI86LQBbnsgnkQ6YFGlWkUzRcSIH4uwxhmwuNr+6WdNtuxZzchxAwOESs54EHTgQSzEMlgURhv/9sufGuWzT1epCA4P88ENgk08nA8V0McggoMgEcImuBhC+eBS796aVVTACPdEWIh+ecpsXdTt3quD1vMDEbPheLTV629P10/0UXeeEGSgz6FeWEBGH8LiMFz5ob/jjfdFby8LKCasxMC9EdztwvoRxJqwWOQVkB6EBejHqPfFl15onpMH4Cjz9m07NK9tW3aYKhXqmMdqNzb3V37ML8Kz6XmN5UjrZ8ftHbJYvmDRTP/b5gvenaGiUpsHPmElDNsxX0GUhu83lLtWLepBBFf9vntUmAlWEBdhER4B89KznfupRUm4CwbPvJIG+h/eskc6EORCWIDvsA7w5+9/m0kvvmJglQdiTxyHOs0SoYgzLFv8oQotrVVMcO7Svo9YqMxlxk1+XoWYaI8RQ17UhxDdRbSIcfCGzC1oO4jgqtaopGI+lDPcmEDeiZkf7DGoO/op5rR5c950VsX/PVS/9keK+wIBb6g6wSoh5lafGLCPwXxohQrOtCD8g6sjzAWYtzHn4eFNqLBPLGf1GdjNPNmsXqhoAfsw5iE4Qj4QQL00aaZaXwuIFPcj1HkhXL0hzA3VJ21+//z5r86HGK944AM32TgvDnuhr47HYjJ+YbnFGZKrLSEgbNbyCRVSY64EK7RDmw4t1OprpHMkzmkQ32I+gegR7fj1lz5BuLMezu92HsD5O9IQri295iX0M8wfzgBruTiHOIP7WiTYeTXU/BlqPEM4EG6+c5bH/d15LkUfiWbMutNy/8b5pay4wIIguMdzHfVaACIiCAgxbnCNg/kDVoCcIdR5J9JrMjumGzWv78knFFNnWUKd60NdzyGNxLKM9ropmnxQ7wFyrsb1APijHXC94w54qO51jRPqusydhtfvcOfDcNeUNs1wY9bGc39iLOPFgMxZsmi/xL3AH7/9ZYb294l9YG2uc9teem0F67Vwmw5rmng5Bfc2NoTqFzYOP0ngfCAAi5IQMuNaBW4Lh8mLVng5oFKVu0JW/7uvfzT4D5fjeNkH9yV3yH0TAwmkJIGTJ0/I2mj8f5wjEELdgznLhzUZvZ7p1FLPq0MH+M4lzjj4Hul9sPu4SH+HuyaFRV68RGmF/tGO28XvvKdFsZbc8NLrU60bmxr3VfYXES8O4r7iwXsfN3XubWjuu+dR8+H7n/j3wyIdAl6AxL7qdz9sunfqF7Ce5Y8c9+Xvv9aoeL9q9UpiCc63DmlFLs64qA/uE/ByAtaWbFi4YLG6S4X4hIEESOD8IYB5Ai+xna0AARys1E0a94rMYWvlheOyKhxWYZyUDfvw/WwHlC8UJ+xDHAYSCEYgWYVwEL7BGtwj1a8U0UQJszrud7DCcDsJJIUA3I6uFetV1e7MrZbI/icCpIa1xZSyLKD98PthvMBu2jcupL/7j5UbXxGrQcxUVqykIVx1WTYVn0E0BpeSEJHAotynXx0MKFYbEadB2IJ8CubLqHFgfe0isYgEMQ8sPX39U7x4Dgc/Vb+AuezCrGrBCFagEL78ITAOtkF0B/FH87oFzTWXZ1OBDqxCQbyxe+8ptf4GC3D4D/eIcOcIa0UQ3/UcsUVFHRDaQPAGC3QQedx9a04tF8rXsWlhZJMgdBHLbNj/oNT5jnI51OUp3F7CCpnNz1pyw8HV78qtVuXALJoAAcglZbKoAAVuAb/9+Yg8pJMH8XF1svUKxQHin1D1RnlgzQxCE4hWkA/aFOHrHwOZJ6Yt2zUurFavYLHsZhGDQRRkLcRpJnF/Pvz8gPaNtk8WUra1q+ZRy12ffXNIhJGBbQmrZKHqFGlbQqRZX6xUoS1hdRAWz1Z+c9CTsS1rYtoSTGH9C2Onbk0fW6dg1Kbt/ESfhOUtBFjCgltPjMn3Vx7Q8YQ2gjUxiFgRrGXGUG0J0WdBSQcBrjMLSH2jCT3bFFErd0gjVHtFk6aNi7kFYi2M+wfjLBHCetgtYlUPcxOEuRAuYhzbULNybrVeed2VMheJpTeEj1f55h+UDwHiLFjgQxykAffEP8v4scHZnsH4hGJq08Gn13jFdojzMK8+JW2FOt5wVXaZa/MbuEbG+Aw3fpFGsIAx+6uISLEe8P2vvnqh76yKExf/+PsRtYQHIWE0+SA9CMpgMQ9CXIwRWJ7DGHGHpxsWUr6YgyEoxfyLeRn9BExxXkD/DSYsdqdnf+/YfUrPDyVFTIv8O8h8DNEyxgFcoUK4WlHma8zDEAW2kPMAwlvL99kk9DNYuwRE4g8lgIe8EPfYRS24RIBIpJpLfDViyDh96xSL21Wq3+1fuHJjtAtZPft1VjHdtddfZeo2fFAeSv0toqLfNTreAIXYwhneWbjE3CcCLmt57rtvftJFtHuq3uWP5o5jdzQSURgsVCGvXmLBCwEWoZwBgghYhoIFKFg0gZU6WMqAqKW6LPhB5ALREixVucMHy1boA4BnxBoZLGzVfug+c811/1PhC+Ja4RXeyIX1CfCBJTksuMI9U94496AQ1Vi3rVu3bNeHDKXEjRrS7NrzGfNsn47iVjv+tuO9pR+pQOSyKy4JKBIWBSGkgnjkyaY+kQfaEAI4pFX/iYc1vpe4APlbl1EFChYIcDVhMwnXhteLpQ2EH8VFLsJKETFCKIQHyBBIwa0gHjzClQ7cVEEwYv/DWlhBsRqGADEQ3FTagIXclm0aq3gpU+aMajlvyMi+WqeLLikr9auuwkArSlgqIji02+ON6yqLpuJCF7+3iitVG3aLpa+OYkXvpLy1PVIEds78bBz7ifwhgIPgw7r3sYJKPxN5Qxp9Bw+TYcnIBlgbsXX8XR7AIrwlC7cQr+At5gsvKqOLy9j+qbzJnE4mtc7PtlEhGSybLJy/yMBaHPJGuPra/6lQ6uG6tdTF2YOP1tQ++MnHn+t++6eVCBvR524sd52M2Uq6GYJIWFFEf7/z7tsN+q8zQFwH10awPtji6Ua6C23nDnhAjAdgj9Svbe6qdLvWGwJMCCF+FDeAth1wHER1/Yf2kLIWMj0691e3qzY9r7Ecaf2c4xZjFw/m8DAOwhhrJdHmo31LtmcU160QUCIO5hO4ucWDcVgThPAPrGAlzLYp3MRhfnqsQR2dC8CzrYh70M54yOFLJ5MKC/AdIhW4nUWA+0y47MUcgvrffNtNtjjqau19GcP31qzq3wYhI7jBihesPmEs1xYrhtgOoQjmlYcefUBFdhBKQZQDlzAIp0+fCjsmEC8x8wP6CuZk1B1Wt8Bp6aIPkFyCEKpfu8c73NeEqpMVz7bt2ELLfdXVV3hasoTVS4w3zG+Yt2Hhc4m40oElRTvu8GkfyKDQELRVEqGilxWHBJWK2wBRLcYF8nlYRLsIv/38R4J5DPUKd14IVe9wfTKuODqXQ8yJ8fpoPZ9VpwZiFQ7zHPonROV4UGZFyTguOdsS53Kwf37QWBV/Y36DOyQEO55gRcJrjtRI8gfWWmGBEnGeFSEVAqzEhWpL5zygB0TwJ1hb2kO95iXsc1u0xHjH+cMZ3NciXufVcPNnuPEcbr5zlsf93XkuxTwZasy6j43kd2s5h5QWV+QQ6eKlCczJOBdh3OAlCcxbq78IPKeEOu9Eek3mHNNefMIxtXULda4PdT2H4xPLMprrpmjzWfPPv2o5BtcL4I926Ny9ra2u/zNYvfWcGuS6zH9wiC/hzof+uSHEfQGSDzdmgxUBlkcRYJEGLuhxL4CXPOx13Ddf/aDXpOi3cOEO8cBTrRvpMe+JWN2GYHzsfn6SwPlGoMHDzVXUgnUBjJ/ics0bKnRs29PgP6y+QpCN+2Wnq+xQx3IfCcSKQJsWXfUFLbykhf/9ej2vSYe7B7P5d+vVznc9Iy/g1HroXr1H3Lxpi93t/4z0Pth/QCK+hLomhVW5Ji0aJhiXkYxbWH+D1XFYknOujdR7/CFdh7FFxX09rtcbt2hg2okwECLBAX2Gq5ANcWBRG2tAd1a8TV8SxHUh1g7Gjphsk0jw+d6Sj/S68ebbbtR1MaypLBJXpxD1O8Oxo+KlSfjjPgeCeoR/16xTS9S15J71+LH4ZwTO4/idBEjg3CWAe4pQIq/krLlTPNaybWNTpUZFzQ7fbbAiOfs7pT8hxLPCPC9O2AaGNl5Kl4/5pQ0CCZ8Ax7jccIlq/0MUt4FuUmNMmMlZAn+tPaZf3/t0v1kRZ2kMIiuE7Tt9i7wQF8AyF6xQQUjx2P3xb0aXEBedf689aoZP2a7uO63Lv5PxRsQ0LYgWbIAw4uSpMyZL5viNeXKK+ykRuDiD09T6tVfCotAus32Xb2HLGe9PsX6EMHZ6/IPOY8d9F82wUjR04jZ/dFgagyACVo1WrD6o1sbguhLiDQRb56sdYjVYN/IKEO/ZAOt2cGOK42GBCkIbBIg/4MoSAYKXxAQnJ/CHwOTI0dNqScqmh3pZcYoXhx0ieMSxweqNdEqJIAxuJOEC9d/1x1W0g+1oK2dITFs6j7n2imwiaDxkUCb0LWf489+jmu8z/Tb6Nx85cka3/SbW0+Ay1wbrYjFYnSJtyyyOdkTa/7skqwrK4BIS1rpsAOP2YnkMITFtmV0sFtoAMRD6xiFXn7f7Q31CqImxAitYLXqsD4hq6xxpWwYcHOEPp5ApVHtFmFxAtIw+zZ9uK5DP96NYofg+gj6McPRYfJ90upbF3ALraDvi5ok/ZW4qUjBTQHtBNPa2iKQ2yTiCa1KESNozUqZe4xV5/L3ON0/BdaoNcIOM/wih5jE7fu1x7s9y1+TQ+WzthmPmi28PqTtl1A2C5CaPFJC0j5qqcZbYwuXjTBtjAME5H+K3PANMECB2syGviDYR0E7yvC1JoYEIs78Q7LkAAEAASURBVGFts1Uvn+tciOwq3ZZTBXWffuUT/V0vAkcbMC+DMQTezhCsXZxx+D2eAAQ0fcQtIBb5fpWH/gh3iYDGGfBwG2+F4uEm3DVh0QyiJXfAQhXcV+XIgfO4L9x08w3GjHvZbBS3TNjnDnBTASFKp+5t/LuWiPUrCOYgHkPwimMjZ3RMJng4igU5WFNyBiuagQtELFxi4c8Zbrnd50YK7h2LFgsUxMPiHMQ/zZ9s5z/EuvDEBjCD+CCTCHBssK5Z7W/3Z2OxaNJJLFQ8UK2eiqUgzIEAw5YTby8vfvt9fdPXfWwGR32tMKi4CFdsyCPiRoRjx3zzkN0e6WckbQiRB6wBQYCIN4VhyQnuObDt0CGfhUbEmTHtVbHAN9+fNazI2Db1b4z7grrYAPd/heTBLIRESBMuJm04KRed4A+xHaw22QDBAiwlIRw9clQ/bd4QcOEBaajgzB/tgIfDh8RVKsJmsYpgH/jbNDI4LhiHDx6jfRj7cNxrb72sC7dwI/L4o0/ZQ/QTYiEECKhgXXD08Im64P5Ygwd1O/7gYdPfwhUWFb6SB1B2sf2U64IbnGywLkWKFo3vv2CNvusMGTLGz994YIyAfu8Of4slOIT5c98yEB0i2LfqYW0wX5w4BG7x0A8QMO7Rr78Wi4BWIOMey4gXaf3seMAxiQ0Q+27MsMnMFjfJcHsJ0SKC5fXPX//q7+tu8LlIww+UDxargoWrr/OJ2F58YYpaToM1Qoh2Idi0AYIK9AWnOO4PsSSHMGzAaBtNxaP4sX37DhWpNmv1hIolYTmr/5BnDR50IIQbExpJ/iRmfsDYcQaUGeJM2952H4TSeCARrF/DlbMdczjGupENVieIEBHgatEG57iy27K4Li4gWIDFpJ9EjAv3nDZAKAMLAgjZHW697P5wn845wAqGMX686hXuvIC8gtU7XJ+05YSo0wa4BEVwzmN58+XWbUcdc31ytyVcLcMVNkJnOWdD1IsQbo7USPLHaakKAjKIdnDtAWtMsOpkA9rShsTMA8Ha0qbpNS9h32m7MBIX8fSZ0yqutceFuhaxcfAZbv6MdDw704z0u7Pu4cZspGk64znXbHAeOyFCJPsSA+IVKVbIf+60xwU770RyTWbTCDemI2Xq5OM+14e6nksKS/T1SK+bos3HWv+FwNwG53Wx3Raq3jZOYj7DnQ/DXVNeeLHvvOksn3P+DVcmCN9xzQ0LqhD14wWAuyvf4bca+8evvpcTrr7mCn9SeMEE5+cN6+PXoJz5u/uF/0B+IYHziMCbi2fp+RkvRuB+BKI2vGwRLHz0xTu6Cy8lQOyCMZk7Ty6/Nedgx3E7CcSSACyjli7teyaDdPOIeAsh3D2YRpI/mTLFr0f71q3eUCv7TvepiIt7IYRw98EaKZF/wl2TeiUbybjFC6NYR3nosQe8kvBvwzqFM2AtpFXTTgaeJHD9jheonNb9IWrDi10ff7hSXnbpEHBtiHQgwMP1d3nxLrB//0FNGi5QYREW9724TrIB61h4EQr36xDK4d4PngBw7q4gwjvcAzKQAAmcfwQg5EppN6ROURleel/z9zr/S4AXXdJY5sOyKi5DayxfusJc1DZ+TTAlWwjlgDAPgjxw+jdu3RNlcIr0EIdWNVOyZdJWXvErkMlQ7pIigoMVOGsZDkI4iOIYSCA5CBwXl54IN4l1ogvjRCE2n8sdgg0rQINw54iIG3Jm9y1yfyAWl15+fZdaAHpaXI4WFdFKzxGbbRIx+zwmloUQfLkGJmvrUEMsrmXOHP8wD7FKikvEWaNK+w+wi/OwzLRLLIohQGRig+u5j90c9vPY8bjySQFf6FPcmHidjrGivLCJRBEBwh13vV6c6Xto6cUBVvgQgtUb+ybN2alWq2BlDMLHE+KS9fnJgcIBxEtqOH4iUPDoTA+iOwitalWJF1tiP9q9/LU5pM7xQgq76B6sToltSyvm9GJs9znLnNLfT8QJE0uIhaw7RBTkDAXjxGMp1Zah2stZrpT8DuNN6URkaIMYGwoImL8QnELWgAhBfiSVqS2Rw4p7QE6h5jE7fgMOcPy46jKfu5tvfzmiLlIfF6ttF5fObJZ8vF9FnXDdDLEcQjT5WJGhU8jqyDZFvkK4OGlQSbVM97lYhpz91m51AwxX3Da4y3dM2hhCU4bEE4B1GSwqrfxktS4qQdTlFivhIR7+l72otFrggRUFWFSw51ln7na+ttuOHPEJcZwLi3YfPt9b+qFamcFiGoJdQBsyoo/+xh93HP8Ojy+w1uN+g9YdzV3uI3HCKaewyB4DaxNYhLPWJex2ZxpnXG+x2jjBPmGV6t3lr5kvVn6lopfnxe3MG/PeNhOmjTJZsmRWq3xYoLy7SoVgSSTr9nBtCAt0M16aa6697iplA0tOEPN9vvJLdV+B/agHXOnByocNEGnA8k+4ACFb62ZdTM5cOVQMCAtd34rVrAljXtJDLfvTce5OgqWHfo2HrHAfeFuF8rpgGyxuqO0od/r0gWIhZ/zxUwNFU3Zx/H/y4LVipTucUYWXzyIeNm6PE6Dt3rVXxXv2QSwsNOKNa7i+hdtNiA0gNIx1wFhBsDyd6VsrK3D/6RR4IQ6ES/viHgCcclhRvOzySzSJnTt36afXWMaOlKof8oJQE9YBMK81bt7QlCpTwrwyZY7OY9hvzymwJhRpwFwGF2v//P2vPAxYrQvzcPEIy3gQtCIsemuZWFm7N0Aga8WpsPDnFI4gPqzNIUB8CRd3CNtFfGxDuDFh48XiM9hb9hC8IATr1zeVvyHBeEf8YHWy861X/8NxwYIVfpUTkfWHn7/tj5ZOLGp+89V3/t+x+uI1j0VyXghW73B9MlblRjqxbsudO3b6i7dn9z5xSewT3oabI/0Hub7g3AvroLAg6m7LaZNnumLH5meweQnneexzBlh2tSJEbI/0WiTc/JlS4zncmHXWNaW+e5133HNAqGuyYOWMFdNg13NJZRnpddPBuGukYPOsu/5eojd3nOT8Hcn5MNw1ZVLLByvMcIn+xWdfigDnQxHVfqAWMzt0fTo+aXuyly0QeUPkHOy+JP4gfiOB85cA1gLwH8JRvNyB/6GEcPbeG9ezsCy14LW39RrZul48f0my5ilJ4IorL9OX19x5RnIPluCYEPfJ9kWlUPfB7vSi+R3JNalXeuHGLSyvzZkxX1/EhMX6aMJlV/heXILF9GABL8dBlA5BLNYvnGHVZ1/ruRfCN/x3hmWLPwgQwp2WhXTcW8CyJES1sH4HER3urSFWd4sEnWnxOwmQQNonMFysO7sD3I+ejeAUlOE7rMEtX+YrCURxF4oweM0/a3UDvqdUsAI9pzAQAjewcwrfUB6Uzwrl3OXzSscdh7/PHwIxFcJB8GbDreIKFaI36xIV2+Ei1Qrh3HHtcfwkgcQSgLs5hMxine2+u+MFl7A6BVeMCLDEBetp5a7Jru5LJ4tgqlNz3yI3LIhBUAK3eVhLiqXoCyIbG376w/fQvJi4l3QH1GH192JJQ0R4t97gE3sgDqwZ5RZLc15hwRKxACPuOW+/KYeBuAL1qCIWk6yFsh8lv3LX+kRXh4/Gl8OZ1qHDp/2WpH4Rt4QIOD6hAMP7+Hxi3WpbnNU9HBstOytSwbEIkXAIVm+87A7XjbCMB1e1CHDxGqsAsZrlAjeNCJa1Mw+4D/1LLPzdfmMOtWCHfRCfYTEc/ctdZ+wPViebfri2PHBIKu8Iv685qmK8TBkjE9LkiLOEtXWH72EgkoLFvuQK+fP4XAuj3F5jNpq2PONQZSWmP4Zqr+SqvzvdkyLYtGGzuCZGfylWyHeahhAW7onhVhduYBG+//Wwfl5aNrypMssnGqaauMcfCBeNzFMoj7VACbfDH35xwLR+omBE49cjWd2EvgrXr++IpTuMtfIyd8F6Hqy0TROhMuboS+LqG8k8YfPJKYJbjNsff/ONWWxHl8FcJZ7logooV7QBbTnl1V3qShZutfF/sYj75ogYDhzh1hgBbrWv/5+vQDjmv03H1W13tPkxfjwBCJYqV69o3haXobD2AzGHDS2bdDCnT0nbzBhjN4kgyDdfon84nivp/osvuVDfAoXYCcI5hO9FwIQAl2rucEKsSr39xhKxLtfEv+ujDz5V11pwu4bgFccfGfvjxBnYhvLDelawhXq8zQ5x1KrPv/K7FcVxsBSF7UWKint4l7iqrCwSfvrx5+YOEXdZKxV4kGsf3Nr9x8RFA1giLJj3jrqH9LKah2NhVQ8iDljjw//FsrCHt+3/ELeasNK1+N33VVgGy0EpHSJpQyxyThw7TRcmsSCJUOHu20z/XsOUY+v2zXSbr694X5vZOVcjuv6sXbte31RuIWJLvHWH8JNY5rPBtiOspVmBItoN7niu+N+lfvEZ3KbeX7uaadKgjXnu2SFm6syxAdYKbXrhPkuJG7gP3luhAiUrVjscZy0Ox2Kh1hmwCI3+BLGY0/ogLL3YhXNYQIJ4Ci4p33lzqZkoVhOtFSRYX4B7ObiBRD/DG9SxCk43f7BqhVDMYVHQ5lNCLCsiZMma2bMO1kod2gAiM4Q///hbP61VOvdY1p3yJyn1c1u4s2kG+/z+2590F8SU1q3TKYfVp+IlffWEq0S4f0GAhbxpk2fJ4vu9/m1Oly1wb4s30eHOFgLeJ5rUNQ/f/6RYDluqQrg1f69VAR7cHTsDXLIhFJMHA5YZfjvnS7ihRoB7GVicu0Wss8GFc7gxoQcl8o+zTyAJtCncoyamX8toSFCKYHUqXrKYxoUVJmtV0AqnnYm4xUnfS1vhAVHmzAnvE53H2e/WGsS2bdv97oePuCwl2rhen17zmJ33g50XkE6weofrk15liHRbcrYl+unQ/qMNrDdArDOo7wgzbfY4FXWGmyNt+Z1tCfehOGdDGI55zt3f7DHOz6S2JdIKNi/B3afT+igesqFvwgoFQrhrEY0U9yfc/AkX2hC7BzvH2bSc8521iLbVYfHWa7zYY/EZybnIGT+5vjv7pfO8Y8/loa7J/lu3PmixnHxiMUface11PYeXP8Kd14MWVHZEet0UbZsVu8BnERguy+PnUd9aVajyeO0LdV3mFR/bwp0PI7mmDJZ2JNth5XXR28vULSxcw+I/XIu//eYScefYzJQu67MMBD548Qfht1/+1E+nNVLdwD8kcJ4TwHm+ZpW6em8Vfw/ru++3972RIMKLADjHYc5kIIHUQCCSezCUE33Xejb4WayUIbgFXdhWsrTv/jHUfTDiJTaEuyZ1phvNuP3yi6/VdfEz4uo0VECaj9VqrNatH3ykpka1VlThMhUBlv/fWrDYvLpwmt+rgbW8h2sZd4BFN9y/DR7RO2DXG7Juhn1wwWzZ2wjV7rtH15u6dXhORXT31qxid/GTBEiABFKEAIRva8at1bwgKKtiKuoa9UWXlNFtEJ3Z4BSl2W3J9blcrL4heOVpLcNZgV4wERyOD5UO9jOcXwTSx6q6ELbB+husvlkrcEjbCuKse1Rsc8edv8x3AYZ9DCSQWAIXiRW40sUzq1vPl+btMr+JuODVd/aYluKG7t/1x1TwMPqV7epusM0ThVQk9e0vhw0EHAiw1gNrQ/OX7DGffXPQ9B29RX/v3X8ysUXyHzdh1k4RrRwx7364z8yVMkGQATem7nDP7bl039TXdqnLQ4jSYMmsff9NnuIyiCTgGvFGsYLX+vFC5qJSWczMN3arO1OwgIDqIxGnwMUp6jl4/FZ3lvq7/7ituh/cVn9/SDkWKegT4Hge4Np483U5zG6xSvfW+/tUhDboRe98XIcF/RmOQ6h6w2oWOPwq7GDlD8LAIXEuZffuj9wiRrDCDZ+8XdMGK7RpmRKZTSFxY+kOD8aJ8PqN2aqWrMC/65DN4nrX2zJdqDpF2pbbdp4w46bv8Lf1QRGYVY5zI+kun9dviIwgQvpcXFLCFSX+j52xwytqTLZBZFKjYm7tO3D7+4OIlD6UNmvdZ6N594N9IkgJ35Y5s2fQMfOdWBD7Q4R/CInpj9G2V0wAuBJZ9CHcOh9Ui2HWDXJVEUwhwLohQq+RW3Q/3Be//+kBU7ZkZrUWqTs9/rj5RMLUI5mATffc5punJszead5feUD7yUQRFUO8B5FeuPEbkJjHD4jfIDaDu2rrQhbCYMzP14irZytOijYfjAW4egZbiGXHyPkAQuloAgSTCBgb1m10JMfDOiT655RXd2obY3766odDeijm2rIyj2DeQvtjboE4zrpPdopEI8mLcRISqFy1oj6Qxp5bHW5PbrmtnAo6poyfrhaU4I5u5YpVBtYXrCDOmVrdhg/qT7g4W/351yoge3XWGyr6sIITiL5mvfKaxvtK3hjFg8yK4sbIBgjj7n2giroCxDavODYuPrEI98lHn2t+/XsP013VZdEsWMAbpXDFOvC5EeqGYd6cN/XhN0RTXuHRerV0c8+uA/XNdrh9bP90dzNs4Gjd/pBYd0KAe9mvVn9r3hJBIQQs9s14+5ASYrpN4mITb7H+JC5khw8aq2/DQhC1WhYlEbCwiDdowbhGzcq6LaX/RNKGZS8srUIttJ0VHZaHC1wJ2AZXFsECFjjxYORrEdv8Gecq0h0X6SMO+soXn32lVgXGjJik0SCkwMMYCOC+E5HlmJGTtP1eHD1VH3ziwboNcFcLQeaAoT21f7/w/AS7K6rP+2tV0/jdOvZTq3dvzn/XvPH6O0HTQPkaNnpUx87gfqMMXIPBDUnd2k3Mu2IpDA/Zh/R/Qd34QqDVRhZ+4fYDfQvhZnEZggdI88Wawuefrjboe/i9S/pGUgMYQOyB/CaOfVk53xonZHOmDZcnEKNi/L40aab5/dc/9fsDVeuZf9es04VsiLXAAosnGIPjRk3R9OzDZvdYtukntn4QZyFgfEBEE0m4Ls7965wZr+sb6hBLYixCyAehC+oJoQtceqKu3379g4pS4Zb3AnGRilBKXOyg/hC/weoQBDuwdIE+h/GLuqN97IOSZWKRBu5jYEHTGSpXq6h8Rg4br331l59/N0PFTSqEmugTyBPzZodurQ1c+0AMif0oZ7gx4cwn2u94cx91//nH33ReBJtacQJXZ1rh+rUzrv0eqk5wYYdxDouYi995T9vlhWEJxyjc5aDdMD4wb29Yv8nUeeR+m0XYzyvEgiHyse2LvKZMmBH2uFARwp0XQtU7XJ8MlW+4fcnZlhA/o5/DjXnHbm20HWa+7DuXRzpH4ny9cMEiPZf26jpAq4OHXJGGWLRlsHnpXnGZvk5E2LAgirHeW87pGAuY5xDCXYs46xBu/oxkPLvnO1ivgtspCIkhPsI86HSz7Mzffk/MmLXHxvIz1Hkn2msyWy43n0iY2mODfYa6nksqS5QPc3q466Zo88HDfbgTmz39de27mCf7yUsJ0YRIrsuCpRfufBjJNWWwtO32j8QSFe5DvEL+AnnVlTeExzh/4z9eKgFrWF7FeRffRwx5Ua+/cJ4bL+duPIy3169e6XIbCZyPBHDPBHeQ74iQFGJSjBfc02LeejiMG0Vcv+I/juvSzid0seP/fGTJOqcuAuHuwWxp8eIczqO49104f5HeC+NFSXfAOSTcfXDfnkP1mtd9bCS/w12T7pL7YJz31v27Xtc6Ih23c2Yu0PUHvITiDBDCjxfL+7B0joC54D5Z/5j18jy9z8f9xcihL+q+O+66VT+xH/cFo4dP0LWOWdPn6YuD8BLgtnwON+5IA/cLeInM+d/ec3768RearvNPwYL59doX9364X7eCRmccficBEiCB5CTgFpFZ4RvcoNrvyB/is9QUrAtUd/lTUxlZltRHIKF6IxFltOI3WH+D8A3CNmwLFrDPxsV3G99aiwt2HLeTQDgC3VoWMXCrCfEX/kPYU79WPnOhCAxgTQjCoK5PFdHtD1bLYz5edcC8OGuHmXx5SQM3nLAcBjEXAkQJEGE4LZ2587eCjHDbYaHKCqBQpm4tC5tsWeN1qDYdWH3r276oCiDmLfI9EIQ1u84tCpssYunOHUZN264ioGZ1fS6H4NK106BNyqBvu2Ja18ETtqoYDIKwi+Ncxtr84KcTojyU74WXt2vyhQpkNB2bFXZnpb/tce6S3HlzThV5vL7YV2aIc6zVNN+BCZOLM7qTcIdsCcchXL0bPZzfTBExIVzdIlhreZu2ntDfXn9s3dz73NthIW1QnKCwSMFMpmPTQFY2Pixlwbog+t1EEQwhQND2tAgWvUK4OqHfhmxLSRQinb/XHTOrRMyIAMtSj9SIf2iuG+P+2HK62/LR+/KaVxbsVveyiAphHAQ8znj2WGd6+A6hIawXeoXC0q9scKb12P151XXte5/uN7CWiH4DC4b3xll1DNeWEHYhLkSm/cduNXPHlDGJ6Y+h2ivSeqF+zrrZ+vo/PXY6WRYR62/TRARlLY49dn8+c+XFWfVwtENrEfBCSDVOxIngdMXFWUwH6X9Iw6bjzsKLTzimXpVwjlfMST3bFDGjX9lhpi/wjTEIQru08JmHDzV+MX627QzeRyAYgyh55pu7A6xiwrIiRJLlr/NZSwOUUPlgvrRMbH3q1swneZ9QwSX6GuY6zMc2pI87wH8cdsTv1mgQMC9ZsV/HR62qeTwFzYiIsrnDczIn4zyANkTA/NGiXkG/Rcmucl6AkNWev3D+wRxy7RVxbxu7yoI0nO2C3wzeBLB4hgW9G8tda7Jl840pxGzw5KP6ZiwsV+E/QvX7Kusilv6I+5MOA0kC3KcMFpemEHl179RPxQe3VbhZH5xb4RyEPXjYDOEZhE5YmLRWsiCMwr7+Q3vEpWwSxPHviPty6eUXqygNi/QIcIV0xf8u0+94oOgOcEsIq2xY4ISVLzwga9y8gbpxccfFb7BBeSDQ6NVtoEaBQAgCJgRYcOvZt5PBg14s8CE8Ii4tm7RooN/LXFjKqEssEQD8I5aiBj3fywwZ2dcM7jfSYIEUAXn0G/ysKXZBETNfxIYok3uBEvHSZ0gv4zb+2gjbEJz1tN9lhPt2Bvnrj+cf1774kbQhjr2r0u0q3Pnf1ZdrDnBjCitXsFZj3eV5ZQ2BINoeVvEgcluxepFGs/0DPyAW7NarvT7U7dG5v/YjLJLi4crmTVtUZFTv8YcNrOFA3IKFajCDG1GIsCBW0hBXN4iSnun0lIppbip/nbk8zrWHk5Azf9/B8X+x8GqP79nlN80LaUJQGSw81uBBtSAEUQVEMRDiQPCBN5mnv/SqikjGTBwqc2wmFfUsXbTcDBYrJq+++ZKpcX8V8/df//pdweJhLYQV69dtCMzO0b/97e2olGO3/zi4LGr/9LP6G8yeF9P5zjHvjyhfevXtrEJDPGDHf8Tv0qOtsa5U2ndppSItWF9BgDAB6eXOk0tFju6xrJHkT7j62b5p49tPCPbKlC0lApVB2h72DXU7/9h4Tpfp6M+NmtVXy5N42xzHV6h4q4pIdu7YLeLTgmoFEw8T0CcREGfyKy8Ya5ERVgUhHnymZTexKjjG3C5zWjt5kx6uWmDND20Ly3/NWj2h1vsWi8jQWkW0ZcInHiqMmzJchHZjdL7ANuTVf8izBhYGRw+fqNa2Kle9S8d0h66tTB95IIN+Dys34cZEYueHkmL9D+7kbP1RF8yTCOniOpHtVqH6tR7g+LNnNx6OhK7TkJHPmb49hgoTnyU8zK0Qyfr7s6SH8ffbr3+o6BLJo3yP1K3lyCn+q9f8CKEGxIUzZQ7u1LaXthfmKjywRfXi62hr6dgWvyk+E/kW6rwQSb1D9ckcHmZ4LQ9bVhTG+d0WLlRb2lOCPS6atsRDQYwfuLq2lhUx/jAv4FwQbo60eeKh4cxpr+mDM5QZgk+41vYK9hjnvqS2pdc1hk0fbqzXN9sg8/NcFdhjO6xyWitb7usVe5zz01nmcPNnuPHsNd/VbfCQWOM7pMJ75AthHB4OOuc897ksVDtv27o9qPtc+/KCf371OKHYfulkgO/uMoQ674S7JvMa08jDi084pl5lwzYbwl3PJYUlmER63RQqH1tW52evfl30ehJCUwRccyI4+6O7TTRC3B/3ddnby+aa/fu816pxr+AMoc6HiBfumtKW0dmX/Nvi5l9YbYaAvbG8yIIXHJwB1yW4lsI5HPM7AuqPlxEQcH4ePXGIvHwwyv8QH/NVz36djfPFiVB8NCH+IYFzjICd0t2XOX0HdTOj5IUN58tDrdo2Mbjf9gp2vOK+3wZYgce9McY/AwmkBAHbD22/ducZ6h4MFv1xPM4XsGRmhZywFN6tZzt3Uv7foe6DYake5y5Y9Q4VbHnd4zDcNSnck+KlNlhpxlpTJOMW1ojxchGuldznvBMnThjcv8KF7B13+a4h6slLhwgTxvhehMH5tu+g7romge0QqWH9Cvep7Vp1xyaDl5zAxR0+Wv6JbqpUuYJ7l6zrXKprCLjOrnF/Zd1v153x44HaNfTlUbhJtcG2t/3NTxIgARJITgIQk8HlKNyIwj2qtaKG7bAYh8/UGFKbOC81MmKZAgmkO3DgwJnATYn79cL01X5xm7X4hpSsa1SI3CCSwz6I3/AbrlJt3A6NfBcjics98KgD+w/pw7bArefXr117z5gCed2Xm+cPgxPiYhDuPq01oWhqDnd08CoEV3xJDV+KxR9Y9enbvpgI6zKbfWJ9KH/ewAWuYHkcPnJaXfflEJd+iQ2wdpRTLDTBnSKEIbCaBaFgcxHO3X1rroBkUe/jx8/43cgG7IzwB5jDgxbELLEKSeEAURYEh5G6Bg1V5pdEWPeRCCchtIqWFYRUKIeXmDFUns590bQl6p05U7oktQPKnDNHepMxQ+TzCCxZQcTjFdo+WShA2OSOAzeEeyRPiDLtDbMzTri2RN9DcI6XxPZHd3slpV7OOgT7jj7erPt685CIFmGZbve+kzp3BYhJHQfv2QdXyeLqM4q28eITjqkjy6BfMbdgfAUb8+7xm1ws3fl4FRjnhWMyx2WUOeq4WJfDPP907w3qfrV9k0BBq9fxzm27xALmP+uOq1U553b7/S4RB2PML/tE3J+OLhPQp1GG4ydO+13c2mPsJyzfHRbBYB4RHDKkDAEs5u0RN4958uRO8BAqWAnwhigEbnhw7QxwMXjmzGkV2D35mAg9BnY11994jUbBQzy4LBr2Ql/9DVeS7jg2LQjf7q30iApiIBKBJbW8+fIkyM/Gd3+iHHuljG7XDSdPnhRLobVN81ZPquU753GwRpYte9YEb7naOCgD3Ke664z9XsfCChTcXmJx1obOz/RWUYF9iGe3n43PYG0Yi7LA9QiC2wWGO22UIU+ePAkWbG089E2wdbej3R+rT8vC5tW0YRsR4h01c994KWQW6Gf79srYETe37kXnkAfKTgg2YQ0MC/NJDbBW2LX9cyrkgpDN1iOSdOES8NChQ35hmPsY9GOID63rYOx3j2X3Mfid2PrhLXg8wLZWF73Sdm8DR8wnVtzm3o/fqMcxYQ4hnztgXoCbGDwEcAaInpxtC8EQLJy9Mme8gTg0WMD8hbfvw/V/r+PDjQmvY4Jtg/vrAgULqEB3/74DJrM8DIIQNVxISr92po26gCnaFPP31IkzzWuz3zALFs3UByzOuOAP4Wgk5XMe5/yOORp9J5YPULzmdmeewb5H0ieDHeu1PTW0Jc75dm4JNUeGOld61c1rW2LaMpJ5Ce2yW8Z1/vz5/HN2qGsRr7I5t4WbP8ONZ6/5DowhmMyYMbI1E5THa8yizwQTdL+7/LWAaxNnnSL9Hs15J9g1Wbi8vPiEYxouTewP1UdTiqVXPl5lR//MmTOHiiQxh38v1nKflZcI8MAaYslIg70ue1VefIHI1SsMFfHya2JJGX1vxNgBAVHc58OAnfID7eJ1X+CO5/6NcyWu9Wx/h5jfK3+cI2CFBqIGr4D64bohmPjf6xhuI4HzlQCuiTGmnOfC85UF631uEQh3D4b7U6zP2Bc1w9Xe6z74u69/NB3b9jSDhvfWF6jCpRFsv9c1qY0L8Rrui5whKeMW16tusTnSxvkX94hO8bgzT3zHdQjuz9yW4Nzx+JsESOD8JNClXR+tOMRkCO7fujHMn8QcEybJoLuTmldSjw9aMMeOWOURq3QcRTsnvp6vuqHIV5fCNLMVvMEtKgJ+I9jf1vIbBHA2LsRzCDau/uAfEogBAQgzEiOCQ9bBBB1JLRZEK5GK4JBXLIR4vV/YYnKLEK6OWL/bueeUefO9vWpJ6ror460q2Xqh3hE8G7LRPT+dIiTPCInYmBQOXlaZElGEBIdEyyqxfdGZcTRtGYt6J6bMDcT6IiwteoVcHhaynPEgfsufJ1BQ4twfrk5efc9rmzPNYN/ddU9KvYLlEWw7OBQII5bNF4JTsHS9WIRjGiwt53a4Qg0V3OM3uVi68/Eq0yyxMrdaxMmP18mvQlG4okWoUC5epON1nNc2tFHuqzKYF/v5rnWccSDq3LH7pIGLa/Qlt7ATgtgsmYNzg5W6PJmC73fmxe+xIQBhF976jCa4BSP2WJ8YKIMuLi5cOttu1s8nm9YL+I0FSHecgAhxPyBqKBBl+VAOt3gKD5e/+ep7TdXt1hAbnUKfuKwDPtzpOXd6HYuHde4Hdu4Hi840Uvp7sDaMRTkiFQCFKwP6ZijusSgrXG3B0khHsSoFC14Q6qyRNwHd/dUrL/SzUAvHXsfYbUkR/Ng0vD6jZYZF8VACMq9+HAmbxNYv2rEOBnj4HaoOiONVD2xHwEN3r77obltYj5r/znQ9JtSfpIgbvcoRKq9I93kJAIMdm5R+bdPEfFunRkNTX6w73lahvPlQ3AfDHResrRUQiwzu4BQMu/dF+js55gqvuT2S8kTSJyNJxytOSrdltHNkLNohMWlEMi+hXdzXO5Fei3i1Rbj5M9x49prvEtPnvMbs8DEDzInjJ7yKrcIqzx2J3BjuvON1TRZJVl58wjGNJN1Q/SulWHrl41X27uK2Pa+8hPGwWMqEMHDOjPkqoL/2hqu8ogfdZq/LYA3aWlx1RoY4AGK3v/74x9SsXd25S7+7z4fuCIltF1zj4xwMwf1/Yhk3WP7hzhG2fu5y8TcJkEBCAr5r4uju/ROmwi0kkPoIhLsHw/1pNPeoXveP/65Zpy823nLbTUkC4HVNahN0i+CwPSnj1ksEhzRx/Rbu/B6paBDpMZAACZx/BKwA7vyrefLVGFbo1vyz1i8qTL6cmPL5RCBmQjhYe7OhpIjdnG5OrRjOWoFDPMTZIJbh3HFtGvwkARJIOgG47RwrrvZGTPW5PYVLw26tipjECGmSXhqmkBQCaaEtYQEL/8+1cK7W62y009lk+eh9+cz2XSf9rophca9h7fzmRnHFmpgAwbWXuBnW9+CmF+nXE3EoAwmkFgLz5i40c2fOV9eb1kpdaikby3H2CKAvwCXgK1Pm+F36wf1tgycfOXuFYs4kkMYJQMwDSwmTxr3sd70Nl6WdRHAaS4ttaRxTmig+58g00UwJChlOOJTgAG4ISuBssuzWq526/ezYpqeWD4J9uClPrPAMFtO8rKZ9ueob061DXwP3qPeIC++UDj/+8MtZzT+l68v8SIAESIAE0iYBCNNriStPiMgYSIAESIAE0iYBay0tNZYeLlnNUqNiuKSUz7p3TUoaPPbcIRAz16heSKwVOGvxDYI4uEN1iuS8jkvqNrpGNeZ8NXGY1L4T6+Pheg8u8LJkTp/AKlCs8wqVHlzxnTp1JiZW5kLlcy7vOyn8xGp2ktybxoIP2zIWFFNfGnC3C0tgwdyhpr4Sp90SYV4+KC5dIQxOjgCLcJu3nzAF82U86/NFctSPaaYcAVinwNursVpkhNukg+IGpkjR6FwBp1yNmdPZJgDXIDnFlS0staSlANcqx8XyD94Up8goLbVc8pYVbnWMSefpCid5cw5M/dDBQ+KWNctZL0dgqdLWr9TSlml1jkxbrZ02SsvzTsq2E1yiwYVZOGs3iS0V3KZt37bDFLug6Fm5Bjrb+SeWG48jARIgARIgARIgARIgARLwEbACs2gsxSXmmMTytnkl9nh7XDT1s8fwM3UQOF91QzGzCOdsxvnLfvP/hOjNWouzwjgrhMN2+91/AL+QwDlEQLyPJJur1WgwwRUfHgQxJJ5ARnFtmzF5dDNRFYptGRWuNBM5uVwypxkAKVhQzMvJJYJDNeAKtXiRTClYI2Z1rhKIxm1FJAzgNomukyIhdf7GicblYGqiBNcqsR4vqal+LEviCHi51UlcSkk7KkfOHElLgEeLiDB1XFel1TmSXSj2BHjeiT3TUClC6J6cAS+eFC9RLDmzCJn22c4/ZOG4kwRIgARIgARIgARIgARIIM0ToIAtzTchK5BIAjEXwq0Sq28IELhB+OYMdpsVv0Ew16HRLc4o/E4CJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACqZYA3HGu+WetidbyGo5jIAESSD4CYhcltgHuTyF0g7U3/IcYzgrisA9uUrG9pMRBsPtiWwqmRgIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKxJ1ClRkUTragN8XEcAwmQQPIRiLlFOGv1DUXeEGcRzlqAC7Uv+arIlEmABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggNgQgaruoLa27xYYmUyGB2BFId+DAgTOxS85n4Q0uT214pPqVaiEOv2H9Da5TrRU4ax3Oxo3V54H9h0yxC4rEKrk0mc6uvWdMgbzp0mTZWWgSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIHEEThfdUMxF8JZ/BC7WUtwdpv9DLXPxknKJ4VwxpyvHTop/YbHkgAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkEBaJ3C+6obSJ1fDBRPBIb9Q+5KrPEyXBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjg3CSQbEK4cxMXa0UCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJJDaCFAIl9pahOUhARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARKIigCFcFHhYmQSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIHURoBCuNTWIiwPCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAVAQohIsKFyOTAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmkNgIUwqW2FmF5SIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEoiJAIVxUuBiZBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggtRGgEC61tQjLQwIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkEBUBCuGiwsXIJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACqY0AhXCprUVYHhIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggagIZIwqNiOTQAQE1m48E0EsRiEBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBs0egbIl0Zy9z5hxzAhTCxRwpE+QkwT5AAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiSQkgToGjUlaTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShKgEC4laTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShKgEC4laTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShKgEC4laTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShKgEC4laTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShKgEC4laTMvEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBmBOgEC7mSJkgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAShLI6JXZpu3pzS//pDfrt6Q3ew+mM6dPe8VKuW3pRa6XN+cZU6rYaXPVxadN8cJnuUApV3XmRAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkEIZAAiHce19kND/8mSHMYSm7G0K83fvTyf8MWrbrLjtlqt12MmULwdxIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARSJYEA16ivv58p1YngvKhBqIeyMpAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCA3yIcLMGt3RSgi0vVdFBWlJmW4VJ1M7FwJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJJCmCaz5Z61ZvnSFwWc0YfiY/tFEZ1wSIIEkElAh3Kbt6dOEJTh3XWEZ7qqLT5vihcV3KgMJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJxJhAYkRwMS5CVMl1adcnqvjBIlPIF4wMt6dWAiqE++WftGMJzg0SZacQzk2Fv0mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiA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iIAIiMC/h4AEbP+etlBJKpZAuQjhKDKit3G/mJuVIssmbGN532HzMkKkZksb9qVPERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABEfg3ECAc5w/fj3Ol9bzGdjIREIElR6DchHAUUYK2JddQ2rMIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiMDSJ9CiVXPnnnJeDFdoaRDB+e0K3UDpREAESk2gXIVwheZ+0mHVMjzCEVY1VxjVQverdCIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiKwJAkgatugq7y7LUnG2rcIlIVAhQvh9t6xqhfB4T1uvP1he+1QxYdVlUe5sjShthEBERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERCB5ZtAhQnh1ltrBYcnuGAvvbPAh1JlOUI4mQiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiUhUCFKNAQvWGI3viTiYAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiEB5ESh20VZee0zYDyFP+w6b59cQGhUPcOs1QYO3sOgzYSMtEgEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREIECCFSIEC5ajnG/LPRCOMRw0ZCoiOVkIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIlBaAhUvhDPB20vvLswoJ+I4mQiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiUhUCFC+Eo5IvvLChLWbWNCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACJQgUKXEEi0QAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQgUpEQEK4StRYKqoIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiEBJAhLClWSiJSIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgApWIgIRwlaixVFQREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREIGSBCSEK8lES0RABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABCoRAQnhKlFjqagiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIlCVQruUhLRGDxCIz7ZdHi7UBbi4AIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiMASJrBekxWWcA7afUUS8EK4KuYXbuHCisy2/PKi7LJ/FwENEv+u9lBpREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGBZJ+BlZHVrV14PXpW57Mt651L9REAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERKAiCHghXNPGldQdnBGqzGWviAZWHiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiKwrBPwQrgtNqy8QrjKXPZlvXOpfiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiJQEQS8EG6t1Re6bTZZUBH5lWselJmyy0RABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABJZfAl4IR/X332W+W2+tyiMqo6yUWSYCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIrB8E0gL4cDQdr95lcIzHJ7gKKtMBERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABKrFEeBlbYsNF7rPv6/ifppQxf09fQW3cCk7iqticr26tRe5po0X+rIpHGq81fRbBERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABJZfAiWEcKBAaCax2fLbKVRzERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABEahMBDJCo1amgqusIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIgABCeHUD0RABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABCo1AQnhKnXzqfAiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAISwqkPiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIVGoCEsJV6uZT4UVABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABCSEUx8QAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQARGo1AQkhKvUzafCi4AIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIVBMCERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERCBZAI/fD/OPffUy47P0tjgoQNKk1xpRUAEFpOAhHCLCVCbi4AIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAILLsEyiKCW5o0zu3Wr1yyl5CvXDBqJxVIQEK4CoStrERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABERABCoXgeAJTsKwim23IOgT94rlXplzq1KZC6+yi8CyQODZ1/5xX343e6lU5avvZ7unX/lnqeS9LGa6NNvy9fenu/c/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stI9afz3eftv2q53+1emG7RNo+7L3115c3PECVeUfZl9/Yc2GLd9G4octSUIuuUWd2jO+fb1zdfP/0P5994PXzHq5MtlvFT1S9vv0N82537n+vL+/uLXFzWbb7JmqZK4eeuf13n28wmnX1W2/cAjlzSe675YLhtipgQI3GYC2//dduWEdFZg2073Cds89lEl0HHMkSeUCko50P2Nr53b7LrbLm2FrKUDxM953jPLNqSLs/PO+VYJkH30pDPKQfEaOEno66TjP1bGO7+9YjTBlu22f1x5nP8SjHvy3+9QugLM437j5PnachDuP756TlneAW96R3l6x/ag2WwtV5SmK9a3vfldpRuGU0/+RDn5ndBUv/bs5+5cnn7jPm8rV6LnxPUeL3td8463vac8///a6k5p6V72/PO+3XyqDRQmwFKvZN/oXlMnEBKm+3XbxWauYv1+24XsIQe+t1wNm0DUee1BybQcWMwVtDHe6Wnbl+cW+r9R9uHm9920BLWy72ro8NHpArdteS5dWczWcoAzJzJywv+n011F9o6b+WecvFa++Z/nlyoAh7/rA2W0BClyUDcBuO+0Ict0rZPwwvve88Hm05/4fOkCpM4v3dUmkPnWg99YXt+HvfP9ddCcbp+685PK+Pvu9ZZS9e4Tp322OePjn5l1Hlm/5z3/2eW9c9Bb3t2ku9l0Q/Kcp7+w+WxbKSyhybcfcFjpxjcBrVe0B37T7UdeW2mPabsMyQmf09rqB+d8/bwmr708vqp9bSxri0G8sryj3vuh4rztdJCtO+9UakgYNe/fYz9wYvPjH/203P/7v3tu84uLLi4HshPWikUOnuQ9eMS7jynzS5XJtN73cp3/fLevnozL+yMhmlHa1tPdv5784Y+XK9QTlsx7MUG+BF2ynQm6pEvPbOu3v/XdEkpNt7z3artITbtP28VOtj/htwRREthJZYq85vL+zbZn/9QTJWd//iulGkEqaHbb9k/arvjkhF9eqz/8wY+bg9tuUhPUzGsiy8zn5p77vrxJ1z4JQ2Z41nPYe6K7nLnez5X72fYffO/C8rkYm52nA67deQ17XXfHrfcHbVO6gcz7PNVAzvzMF8p+OewdS79H011O9lveH/ncvuRXv26esctT6yKG3qZrySyn7t8s65j3f3jodINGGPZ3YdB2D3tNDlrusGHLc18m/JzXeU5e77Xv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Yw3C1Upws4XbEojb8/nblK1JIC6huUyT8TUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECky6ww07bNc1ZTQnDjbquCcGV6UadwHgECMxZYKxBuFrtrVaAG7Q2o4wzaHrDCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECCy0QEJtW7xSdbeFdrc8AsMEVh82guEECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCSBcYWhEs3p6kIt83WG4+8vakKl2lql6ojT2hEAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwLTCWIFzCbPmXYNu200G4BONqqyG5PE7orQbfdtlxqxKcO699LtNrBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgrgJjCcLVhSYIl9Ybasvzl8wSdOsdt87LLQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQGEVgtUWLFi0eZcRh46TKWyq7JfSWSm+jtPlMM8p8F113fbPRvTYcZdSVdpyrrlncbLD+aivt9tkwAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSWFlhVc0NjqwiXLlETgptLhbe5BueW3m2eIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFVXWBsQbhVHdL2EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBtI7BcgnCjVIWr46SKnEaAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBOYrsMZ8J+w3XbpHPe3sC8u/OnyXHbcqXabmccJvGd5tmUYjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLzFRhrEC7V3fZ8/jbNud+9tITeslLdim+5v00bfKvV4ITg5rvbTEeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECVWCsQbg600EBt0HD6vRuCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAqAKrjzqi8QgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwCQKCMJN4l6xTgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwssBy6Rp15KUbcaUU+OWli1fK7bJRBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECK4/A5huvtvJsjC1pBOG8CMYu4ENi7KRmSIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAAAFdow7AMYgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEJl9AEG7y95E1JECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEBAoJwA3AMIkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHJFxCEm/x9ZA0JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYICAINwAHIMIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYPIFBOEmfx9ZQwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYICAINwDHIAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCYfAFBuMnfR9aQAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAYICMINwDGIAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCZfQBBu8veRNSRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBAQKCcANwDCJAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACByRcQhJv8fWQNCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCAgCDcAByDCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGDyBQThJn8fWUMCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQGCAgCDcAxyACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQmHwBQbjJ30fWkAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQGCAjCDcAxiAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQmX0AQbvL3kTUkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgQECgnADcAwiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgckXEISb/H1kDQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBggIAg3AAcgwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBg8gUE4SZ/H1lDAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBggIAg3AMcgAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEJh8AUG4yd9H1pAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEBggIwg3AMYgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEJl9AEG7y95E1JECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEBAoJwA3AMIkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHJFxCEm/x9ZA0JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYICAINwAHIMIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYPIFBOEmfx9ZQwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYICAINwDHIAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCYfAFBuMnfR9aQAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAYICMINwDGIAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCZfQBBu8veRNSRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBAQKCcANwDCJAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACByRcQhJv8fWQNCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCAgCDcAByDCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGDyBRYkCHfpZdc1+acRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFxC6wx7hnW+Z373UtL+K03ALfxPddt8m/brTeuo7olQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLzFhh7RbgE3047+8LmvDYIl5bQ2zbTobd6m2EZpzckN++tMCEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIrLICqy1atGjxuLa+huAyv4TeatW3VIdL6z6uQblddtyqhOXKCGP6b9F11zcb3WvDMc1txZzNVdcsbjZYf7UVc+WtNQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC8xJYVXNDY60IVwNvCbfV0Fv2Ru73Ps44aXWa8sB/BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgjgJjC8Il0JaKcKkEl+5Qh7XaZWqmEYYbpmU4AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECMwmMLYgXAJtCbd1K7/1W2jtPjW3GTfT5L5GgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTmIzD2INygleiG4LpV4wThBqkZRoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKDBMYShKtBtvPa7lFrN6f1ti68huDyeJcdtypPZ5waiKvzqOO7JUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECowiMJQhXF7RN29VpWkJtCcWddvaFM4/r/YTgavgtAzOeRoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE5iuwxnwn7E5Xg20JwNVqbwnF1TBcrfbWG4Lbdjo4l/HqPLrzdZ8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECAwTGEsQLgvpDbJ1Q24Z3huCy3NpCcn1Tjs1xP8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGC4wNi6Rk2YLaG2cztdnSYMl8pws4XgMq4g3PCdZAwCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQmF1gbEG4hN4Shks3p7Ur1Cy2Pt+7Chmndolaq8f1juMxAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYJjC2IFwWVANtp5194RKV4VL5rVspLvczTnea8sB/BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgjgKrLVq0aPEcpxk4eu0etXZ5WqvEpYvUWimuDputWtzABYwwcNF11zcb3WvDEcZceUe56prFzQbrr7bybqAtI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgKYFVNTc09iBclU3VtwTeavitPp9gXP7V6nH1+XHeCsI1zar6gh7n68i8CBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECKxoAqtqbmiN5bWjukG3GoZLAE4jQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLjFFhuQbjuSgrAdTXcJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFxCqw+zpmZFwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQWGgBQbiFFrc8AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBirgCDcWDnNjAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQWWkAQbqHFLY8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIExiogCDdWTjMjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYUWEIRbaHHLI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGxCgjCjZXTzAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgoQUE4RZa3PIIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYKwCgnBj5TQzAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEFhoAUG4hRa3PAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYq4Ag3Fg5zYwAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEFlpAEG6hxS2PAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMYqIAg3Vk4zI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGFFhCEW2hxyyNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBsQoIwo2V08wIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYKEFBOEWWtzyCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCsAoJwY+U0MwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBYaAFBuIUWtzwCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQGKuAINxYOc2MAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBZaQBBuocUtjwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTGKiAIN1ZOMyNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBhRYQhFtoccsjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgbEKCMKNldPMCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGChBQThFlrc8ggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgrAKCcGPlNDMCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQWGgBQbiFFrc8AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBirgCDcWDnNjAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQWWkAQbqHFLY8AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIExiogCDdWTjMjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYUWEIRbaHHLI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGxCgjCjZXTzAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgoQUE4RZa3PIIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYKwCgnBj5TQzAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEFhoAUG4hRa3PAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYq4Ag3Fg5zYwAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEFlpAEG6hxS2PAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMYqIAg3Vk4zI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGFFhCEW2hxyyNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBsQrcpkG4Sy+7bqwbY2YECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsOoJ3GZBuITgTjv7wubc71666qnbYgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYm8Aa45hTrex2ySwV3ja557rNxu2/bsvj/DtvOgi37dYbdwe7T4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIERhIYSxAuld0GtU123KoMroG5Om7Cb5lWGK6KuCVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBuQoscxCuhtu2aUNtqfzWr6XyW+0Ktd/wPJcwXL/KcbON73kCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIBCBZQ7CdRl7uz/tHZawXG+r1eBqV6m9wz0mQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKDBMYahOsuqFaAS/gtXaCm1ds63rltFbi0hOB2me4+tQ5zS4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIERhFYfZSR5jpODcFlutm6S804qQYnBDdXXeMTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQFdg7BXhuiG42lVqnqutPlcDcPVxHe6WAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjMRWDsQbja3WlWohuK667Uns/fpjwUguuquE+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC8xEYexAu4bZaAS73hd3ms1tMQ4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKjCow9CLft1huXZZ/33UtLIC6PB4XhEpobNHzUDTEeAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECKyaAqsvj81O+G2b6UDcJW3QbbZWu07tdqc627ieJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC/QTGXhGuLiRhuE3arlE1AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwPAWWOQhXuzWtXaEuz5U1bwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0CuwzEG4zDDdoKab027rfVyH1eBc93Gqx2kECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGA+AmMJwvUG2RKCO+3sC/uuT4JwveP3HdGTBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgBIGxBOF6l5OwW6rE9WtCcP1UPEeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC8xVYLkG4rIzA23x3iekIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYC4Cq89lZOMSIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFJExCEm7Q9Yn0IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYE4CgnBz4jIyAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECEyagCDcpO0R60OAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECcxIQhJsTl5EJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYNIE/j9yNTMvnqDOLAAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "id": "8cda6c13-7fee-4284-aacf-81a506a426da", + "metadata": {}, + "source": [ + "![image.png](attachment:95b9b198-55c9-4a67-b0bf-103c9ae0272e.png)" + ] + }, + { + "cell_type": "markdown", + "id": "e1525230-e10c-4f48-b951-bc73642bb3e4", + "metadata": {}, + "source": [ + "And at results:" + ] + }, + { + "attachments": { + "66422f79-9b46-4e07-9796-c1b350c26c9c.png": { + "image/png": "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XMhths8cRdqiAnD5heDCl1deafiIweGTyvR9vAulbbeCA3FLXGvUP7atsYlb19j0HRsszYXiIo346Bjrl9DYdq+VZP3da0J0wXE07bu8R8FHUN57L8L29xjc3xq49qVKPn73zU/WpEkjGzFyqL379kc24beJfgs9e3X37Ujz29wBB+3jA3Gaf88dD9uJo4/2lebmuxKDTzzyX+vdZzcfXNP8fffby17476u+fepN191po089zla4dqpPPPqcKezWqXOHUBhOZQlV9U1Bu6+++M61VO1izVs08+1R16/b4N4n2RPP3G/16idapy7t/ZdNX1gF7RgIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQV0DBNhW/Ku7QOtFu3SOPOaS4q5bJ8soI7TFkgO8mWZoNahvaVnmN2tGxtsFVe4s0lqZusefWzbBJ29ZGmp1n2vaMdBu7ZaX/iY2KtlF1W9noBp0tLp/gm/Zd3qPKB+Jq16ltF1xypt32r3/bdtfG9MUX3vA/AUx8fJz9/fJzC0x26ib/wbUuVYBusSs9eM+dDwer+9cU13p13dr1prarJ59yrH3+6TcuBLfSr6P1gqH05Sljjg8+uvDcnqaqdRo333iPJTVraq+/86yNdtt45MGnbfmylXbkISdb06SmtnLFKl8dTpXoDjviwNA2eIMAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAI7BaZOmbHzQzHfad3KCsTpUIeN2MMmT5zqCmylWq/e3a1121a+CJiySZFGo0YNXXGwIT7TNGXyNKtZs6bfRqRly2pa3RgXSkvNubVUVwHu5fWz7dPkRa6dqpqiFn9oG59sWmTjt662sxv1sD4JjfJsxO87z9SynVDlA3E63f0OGOkrwz3w78dt4YJFlpGR6VuZ9urTwy6/6kJr1bpFgSoqg3jP/f+y119511575R1T9TYNJSn79e9tl15xntWvX89Pi3ftU59+/kF70O3rh+/G+hCeQmwdOraz8y483Qbu3s8vp1977zvC/pw0zac61Xo1Jiar7erxJx5pSUlN7LlnXrEFrgqdwnUaXbt1sjPPOcUG7bFzG34GvxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMALHHvC4dVWIjY21k4/62TfgVLvNbr36OK7U65dsy7HeTVq3NDOOX+MLwS2W89uvjiXCoYF6+VYuAw/RFuU1Y+Jy1El7um10+2bzUvLZC+r07bZbSsn2AWNe9pedXbmurRP7bu8R5Rr4VmySF95H1k+29dFX71qjW9HqoptucfjrgXqKy++5SfffvcNEROTmzYm27Zt23xFt9zrh39W8E5htgYN61uCC8rlN1JTU/0xqUJcTEzOPrc7dqT46nCNXavXWrUS8tsE0xFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKAAgY8/+MI2b97il6hbt7YddOh+BSxdtWa9+dp7NnPGnBwH1a17Zzvm+MoJ/+3ITLe5Ozb640nOSLUzF31jZR0iU9vUZ9vsbWqlqtExrl6+rVT9AmX0K2tvZbSxitiMWqS2btPSV4gL319y8mZbtHCJjfv5t9BkBdQijcR6dQsNw2m96Ogoa9GyWYFhOC2nVGaLls3zhOE0Ly6uprVxpQ8Jw0mDgQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAyQRU1GrO7Hn+p6FrNVqdRsNGDfIcrirEVdZQWK1RjawCYctSt5R5GE7npdDdgpRkf4ral/ZZESNvibWK2Gs57OPu2x+y77/9ObTlNm1aWbv2rUOfeYMAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALVV2DIsEHWo2dX33QzsV5itTqRocN3t06dO+Q45qZJTXJ8rugPSTVq2ZaMtHLdbYoLxcVH1zDtq6LGLhOICwdTRbYbb74iTxW58GV4jwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAtVLoF41C8IFuvHx8b7LZPC5qry2jK1t07avK7fDUbtU7aMiR1SmGxW5w/La17q1623ZshXWoEF9a94iybU7rXbdYMuLhu0igAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAhEFtroqcY+tnWLPr51pGWXUPDXK1fE7pWFnu7Bxb6vlKsRV5NhlAnEVica+EEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFdRWCHa2363sYF9uDqSbYxPaVUp5UYU9MubtLLjqrXweKiYkq1rZKsTCCuJGqsgwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjsYgLzUzbZU2un2cebFllaZkaxzq6Ga496UGIbO6tRd+tQs16x1i3LhQnElaUm20IAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEqrGAqsXN3rHRPtm00L7fvMzmpyQXeDbta9a1EXVa2MEuDNc5rn6lVIULP0ACceEavEcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEELAMy7Tk9FRblbbV5rjKcQvdjz5rqC1qGxeE61gz0ZJq1LK6MbEWbVFVQo1AXJW4DBwEAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAaQWiS7sB1kcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgKggQiKsKV4FjQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQKLUAgbhSE7IBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBqiBAIK4qXAWOAQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoNQCBOJKTcgGEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEqoIAgbiqcBU4BgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgVILEIgrNSEbQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQqAoCBOKqwlXgGBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBEotQCCu1IRsAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoCoIEIirCleBY0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEECi1AIG4UhOyAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgaogQCCuKlwFjgEBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKDUAgTiSk3IBhBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBKqCAIG4qnAVOAYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFSCxCIKzUhG0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEKgKAjVWr1pbFY6DY0AAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgVAI1EmrFlWoDVW3l5E1brHmLpKp2WBwPAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAOQvQMrWcgdk8AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAxQgQiKsYZ/aCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQzgIE4soZmM0jgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghUjACBuIpxZi8IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALlLEAgrpyB2TwCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEDFCBCIqxhn9oIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFDOAgTiyhmYzSOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCFSMAIG4inFmLwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAuUsQCCunIHZPAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQMUIEIirGGf2ggACCCCAAAII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EUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEylyAQFyZk7JBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQqGoC8VExVe2Q/nLHUxHXgEDcX+624oQRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEPjrCQyp3eyvd9JV7Iwr4hoQiKtiF53DQQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgbIXOK9xT6uICmVlf+S7xhZlr2tQ3oNAXHkLs30EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCodIEe8Q3shbajbO86LQnGVeDVUBBO5rLXNSjvEZWcnJxZ3jupyO0nb9pizVskVeQu2RcCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEAVEKBCXBW4CBwCAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBA6QUIxJXekC0ggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghUAQECcVXgInAICCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACpRcgEFd6Q7aAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQBQQIxFWBi8AhIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIlF6AQFzpDdkCAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAFRAgEFcFLgKHgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggUHoBAnGlN2QLCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACVUCAQFwVuAgcAgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQOkFCMSV3pAtIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIVAEBAnFV4CJwCAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAqUXIBBXekO2gAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggUAUECMRVgYvAISCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCJReoEbpN1H1trB61dqqd1AcEQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQLkK7JKBuCZNG5UrGhtHAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCoegK0TK1614QjQgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQKIFAmVWIW7Jiky12P8UdQ/q2Ku4qLI8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAHoEyCcSNnbjExrmfkozWzRKtlfthIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFAagTIJxAUHMNhVe1PArShD1eQUotMrgbiiiLEMAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAQQJlGojLr9qb2ql+9uNcH3w7YHjH0PGMC73jDQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKlE4gu3epFW3vqnNW2afMOm+ZeFY5jIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFDWAhUSiFMILrFOnD/2sa5NKgMBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBshYo90DcH9NW+Opw/Xs0tx6dmpT18bM9BBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBLxAjbJ0+OKneRYbG2NxNWPsuAN7+E2vWrfFmjSsbf16NLPPfpzrW6a+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CCCAAAISFhERcSozOUSEHxKtSkNLH4E9u/fJ4cNHpIIJ9WiAKC2bTjF63EyHqlXjgs91+NAR2bFzl5QvXzZOUC+l16RTmW7ftlM0DJc3KLASauykmugUnPv27ZeKFcrFuS//+Hr/Ou2gVsfLnSe3f1emXj5x4oSZlnGvnUYybzKm60wKjla52rVztw3waBjM306ZuXf1GWhQsUiR6OqA/v0ZYTk9rVJyv+EHIuxUpjqlpgs+pWS8UMeePHnSVvvTKV4LFiqQZucJde6UbNPv+KFDh8w7WCSgEluoMSMjI011tP1SomQx816mzjTVeh59j3RcDSnmSeC3Rn8bI8IP2mp+p3uOEREHzVSvR2xff7At1H0ltE2rZep/B3T62NOdM6Fx2IcAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkHkFCMRl3mfLnSGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACWUogdv6xLHXb3CwCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEBmEyAQl9meKPeDAAIIIH/cUGUAAEAASURBVIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCGRRAQJxWfTBc9sIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQGYTIBCX2Z4o94MAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIZFEBAnFZ9MFz2wgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAZhMgEJfZnij3gwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAghkUYEcWfS+M/Vt97tnfaa+P27u9AJfvFHl9J3ogQACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAJhOgQlwme6DcDgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCQVQXCIiIiTmWmm48IPyRly5XOTLeU5Hs5cFDkxIlM9ViTbMABCCCAAAIIIIAAAggggAACCCCAAAIIIIAA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AQQQQAABBBBAIKkCZcuWlRUroguA6DINgTMhoGG47du3S+3a0f+7+5m4Bs6JAAJpI5DmgTidQlWnU3XNLbtKcW7d7eczawi89/ZI+XJ0bFWyW001tX7XXeXd/PQps2T4K+956/36X2krroWFhXnb3MLkCVNtCO2a66+SoyYcF3UyKmB6Utcv1OcNN/fzNut0qcFNA3vatNKba42a1LeLW7dss4G4of/3tKxZtU5+mPSZdOvZSc41gbTSZUq67nYaVl3RKU21nTTXpwFAd75cuXLZ7RqO0/bVZ2PslKujv35XKlauYMNqLVo1tWE43a/ToDY/p4msW7tRV+X48RPyyYdfSJ16NeWc1s1MlbpdUrJU8dNWiPtr7j/W7aHH7pUh9z0pkydMCxmIa9aisWgo74abr7GBPj1nVFSUfP/tT9LSnG+eGed0benRffLO7sUy59B227VN/jJye4kGUi9P0dMdyn4EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEMhkAvny5bMhJA0kLViwwP7vj5nsFrmdDC6gM65pZTgNw+n7SEMAgcwlkOaBOK0QF1/TKnG0zClwylQ5O26qpvlbjhw5RANt33w51obh7rjnZhvs0ipl74/4xITO6kv1mlXtIYv/XSrPvPCIFCteRD4b9Y18MXqMNGne0AS+mvuHtMsrlq22n2+99oFs2rjFLrfr2EaGPHqv5M+fsv9w7d2zz45X0FehrkDBAnab23fJ5RfIvr377TatEvf/7N0HmBXV/TfwA4KoINUCCoq9oRJ7b4i9xxo1xpLYS2wx+k815jXNEjUaNXaNscceewd7711QUQGRqoDwzu8sc70LS5PdZYFQThH1AABAAElEQVTPybN7p56Z+cy9S57xe38nfqIqXAxd+sxTz+dhRXfdc8dcDS42itBchPj+8se/pw03Xjfdc/cDed9NNt8wv6693ppp9OgxRWW8mlDdJx8PqFSLyxsUv9q3b5cr18X85599kYNtn336Req94c55kwjc/d/vT0jrbbBWnq/r1x233pOrwy23wjJpmx16p6svvz4detSBlfMs99l6+97puWdeTI8XFezKKnnPP/tS+uLzgenQIw+YaiAuwnA//vC+9PX4b8su04PDP87huCsW31worqJiggABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCc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HEK1nPdKgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQaQ6B5YxzEMQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQEMLCMQ1tLD+CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBRBATiGoXZQQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgoQUE4hpaWP8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0CgCAnGNwuwgBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINDQAgJxDS2sfwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoFAGBuEZhdhACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaGgBgbiGFtY/AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDSKgEBcozA7CAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0tIBAXEML658AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEGkVAIK5RmB2EAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBpaQCCuoYX1T4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKNIiAQ1yjMDkKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECDS0gENfQwvonQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgUYREIhrFGYHIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGGFhCIa2hh/RMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAowgIxDUKs4MQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQEMLCMQ1tLD+CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBRBATiGoXZQQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgoQUE4hpaWP8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0CgCAnGNwuwgBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINDQAgJxDS2sfwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoFAGBuEZhdhACBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaGgBgbiGFtY/AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDSKgEBcozA7CAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0tIBAXEML658AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEGkVAIK5RmB2EAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBpaQCCuoYX1T4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKNIiAQ1yjMDkKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECDS0gENfQwvonQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgUYREIhrFGYHIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGGFhCIa2hh/RMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAowgIxDUKs4MQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQEMLCMQ1tLD+CRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBRBATiGoXZQQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgoQUE4hpaWP8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg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gAABAgQIECBAgAABAgQIECBAgAABAgQIECBQPwJyQynVS4W4uB3r9uxaP3dFLwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4HsINP8e+9iFAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0OQGBuCZ3S5wQAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECHwfAYG476NmHwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBocgItmtwZOSECBAgQIEAg/ejoDyjMYgLXnN19Fjtjp0uAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHZT0CFuNnvnroiAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIzJECKsTNkbfdRRMgQIBAUxdQbayp3yHnR4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNUUCFuKZ4V5wTAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECEy3gEDcdJPZgQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSaooBAXFO8K86JAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKZbQCBuusnsQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNUUAgrineFedEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAtMtIBA33WR2IECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGmKCAQ1xTvinMiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgekWEIibbjI7ECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBTFBCIa4p3xTkRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwHQLCMRNN5kdCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKApCgjENcW74pwIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYLoFBOKmm8wOBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAUBQTimuJdcU4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMN0CAnHTTWYHAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGiKAgJxTfGuOCcCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQmG4BgbjpJrMDAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDRFAYG4pnhXnBMBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQITLeAQNx0k9mBAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBJqigEBcU7wrzokAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEpltAIG66yexAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk1RQCCuKd4V50SAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC0y0gEDfdZHYgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgaYoIBDXFO+KcyJAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB6RYQiJtuMjsQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQFMUEIhrinfFOREgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAdAsIxE03mR0IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoCkKCMQ1xbvinAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgugUE4qabzA4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0BQFBOKa4l1xTgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAw3QItpnsPOxCYisDoMSl9M3p8GjM2pfHjp7Kx1QQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQaQaDV3Cm1mrtZaikx1QjaM+8QKsTNPPvZ8sgRhhv19fgUr8Jws+UtdlEECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgVlS4JvRKY0YWZNrmSUvwElPk4BA3DQx2WhaBaIy3Nhvp3Vr2xEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoPIFvx6Wi0JMhDxtPvPGPJBDX+Oaz7RHHFX8wYphUjQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBTFRgr39JUb029nJdAXL0w6iQEmhfvJsOkei8QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0ZYGoEqfNvgIt6vvS+g8YOtkuu3ZuO9l1VhAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgRkRqLdAXAThrr/7tSmeyzo9u6Z1ix+NAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUt0C9BeL6vNA/n9tuW604yTn2K8JyfYv18RNNKG4SIgsIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYAYF6i0QV57H5IZF7TthA6G4UsorAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNSnQPP67Gxa+4pQXFlRblr3sR0BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEJiSwEwJxMUJCcVN6bZYR4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLTK1DvQ6bWdQIxjOpuW62Y+g0YWlldDp1aWWCCAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjMgECDBOLe+mBQevuDwfm0luneMb9Wzy/bvVOuENe/KiA3A9dgVwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgkBokEBeuEYqLVgbiJp7PK/0iQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQL1JNBggbh6Oj/dEJgmgW+//TZ9NeSrvG2Hjh1Ss2bN8vS4cePSkC+HTLJ8mjq1EQECBAgQIECAAAECBAgQIEBgNhUYPGhwat68WWrfoUOtKxw6dGgaO2ZsatWqVWrdpnWtddUzLz77fHrkoYfTsssvl7bcduvqVaYJECBAgAABAgQIECBAgAABAgQIzFSBBgnExZCoy/6kU60Lm3i+1kozBGZQ4ON+/dPPDzki93L0icemjTbbNE8P/GJgOnS/A/P0VTf9J80733wzeCS7EyBAgAABAgQIECBAgAABAgRmfYGf7r1fvohL/3NVatuuXZ6OLxYett9BadSoUWnPffdOu+2952QvtH+/fumhe+9P474dJxA3WSUrCBAgQIAAAQIECBAgQIAAAQIEZoZAgwTiYnjUtz8YnK+nHDK1ej4CcxqBhhK48Jzz02prrZnatGnTUIfQLwECBAgQIECAAAECBAgQIEBgthB4oaj0ttFmm+Rreeett3MYLmbW3XD9vMwvAgQIECBAgAABAgQIECBAgAABArOaQIME4gIhQnHRykDcxPN5pV8EGkAgvsV87RVXp4MOO7jO3ocPH54euf/B9MF776dFu3VLa66zVlqk66J527tuuyMNHjgorbnu2unZJ59OXw4enFZbc430gzVWS3ffflf6uPj281rrrZvn55prrrzP5599np7p+2R67513U+dFuqTeW2+V2rWv+WZ1nSdgIQECBAgQIECAAAECBAgQIEBgJgtsuOnG6dEHH05PF880ykDcc089k89q8SW6p66LdUtRMe6xYljUt954K80zzzz5GcoyxRCpzZs3n+Tsb7vpljT0q6HFc5Et00KdF05vvvZ6eqZ4trL4kt3TBhtvlLf3DGUSNgsIECBAgAABAgQIECBAgAABAgQaQKDBAnENcK66JDDNAnfdenvadPPN0vwThvwod4ww3LGHHJkGDRxYLkpXXHxJOvrE4/LD3wf+d28Ott30n+sr6+8vlnVaYIHKPjH/44P2TzvuuksaE8+BkQAAQABJREFU8Mmn6fjDj658ezp2uuW6G9M/LruoMtxIpSMTBAgQIECAAAECBAgQIECAAIEmIhAhtQjEPfHIY+moE45NLVu2TH0eezyf3aa9e+XXv5z6x/RUnycrZ3zzdTfkwNshRx9RWVZOxJcMP/t0QPHFwtVzIC6+OBjPV+I48eMZSinllQABAgQIECBAgAABAgQIECBAoKEFJv06Zz0cMYZE/flP1sk/MT3xfD0cQhcEJiuw13775HUX/P28NO7bb2tt997b7+TqbWsXVd6u+e8Nade99sjrHy4qxlW3WP+Xc89Ky6+0Ql7comWLdMYF56Td9t4zzz874RvTl1xwUQ7Dbdxr03TG+eek1YuhWqNC3Z1FIE8jQIAAAQIECBAgQIAAAQIECDRVgVVX65nmnXfefHpvvPp6DrP1/6hfnl9ng/XSsKHD0sAvBqYll14qXXDFv9Ipp/42r7v3rv+lbyd63pJXTOWXZyhTAbKaAAECBAgQIECAAAECBAgQIECg3gTqrUJc185tU/8BQ/NPvZ2djgh8D4Htd94xPXzfg7nS2//uuKtWD6v8oGfxAPc36fVXXktX/uuy9P677+b1MTRqddu8GN4jHvj2XG21FA+FY9jUxbt3LwJ249L1V1+bIlg3fvz49NrLr+TdxowZkx4thhAZP35cnv+wGI5VI0CAAAECBAgQIECAAAECBAg0VYGWc8+d1ttogxSV8J996unUeZEu+VTjy4ELLrRQnj71L6cXw6W+ke67657Uv19NWC5WxJcBp6d5hjI9WrYlQIAAAQIECBAgQIAAAQIECBCYUYF6C8SVJ3L93a+Vk1N9Xbdn16luYwMC0yvQohji4+CjDku/+cUp6dYbb661ewzdcdj+P83LVlpl5dSqVata6yeead68poji+HE1QbdyPrb7unj4Wz4AjuFFym9Vx+vYsWMn7so8AQIECBAgQIAAAQIECBAgQKBJCWywyUY5EBfPNbouVvOcbuNem+VzHDVyZDru8KNz5biui3VLSyy15Pc+d89QvjedHQkQIECAAAECBAgQIECAAAECBL6HQL0F4iLc1q2oEjetLSrKaQQaSqDHqqukjTbbND3yQO2hUO+58+58yA023ij9/JcnpCef6JNefO6F73Ua8843Xx5+9ashX6VDjjo89d5mq9zPqy+9nCJspxEgQIAAAQIECBAgQIAAAQIEmrJAPL+IL/YNGjgw/8S5rr3euvmU33r9zRyGa9e+XTrj/HPS8GHD06MPPjzZy2ndunVeN+TLIfl18KDvqvF7hjJZNisIECBAgAABAgQIECBAgAABAgQaQKDeAnFxbkJuDXCHdPm9BX580E/S0336Vqq4RUfLLLds7i+GArng7HPTYw89kueHfPllfp3eXzvt9sN0+UWXpAv+fl56/JFH0zdff1MMJfJmOuznR6VeW/ae3u5sT4AAAQIECBAgQIAAAQIECBBoNIG55porbbbF5umO/96Wj7namqvnL//FTLfui+Vl8UXA8886J7395lt5Pn6NKMJxE7c111k7vffOu+mi885PN/3n+jxdvY1nKNUapgkQIECAAAECBAgQIECAAAECBBpSoGY8yIY8gr4JNIJAs2bNJjlKh44d094H7Pfd8mKbNdddO/XeesscknuqCMtt0rtXXh8Pd0cMH/HdtuXUhG6bTRg6NU10mG132iHtUxwjvk398gsv5TDcxr02TetttEHZg1cCBAgQIECAAAECBAgQIECAQJMVWH/jDSvntuGmG1emO3bqlA467OAckHvw3vvTSiv3yM8/YoP+/fqliZ/FbLL5ZmmNddZK8YwlgnE9V18t99Wsec3DFM9QKrQmCBAgQIAAAQIECBAgQIAAAQIEGlig2bBhw8Y38DEatfthQ0ekLoss3KjHbGoHGzRkfOrUfqLkViOdZBx7VmhjRo9OEXJr0aJ+iiSOHz8+DRs6NLVu0ybFt6s1AgQIECBAgAABAgQIECBAgMDsIBDPPEZ/801qNc8803Q5I0eMyNtO7vmIZyjTxGgjAgQIECBAgAABAgQIECBAoBEEZla2pqEvbWbmhhr62qa1//pJA03r0WxHoIkItJx77no9k/hWdNt27eq1T50RIECAAAECBAgQIECAAAECBGa2QDzzmNYwXJzrfK1bT/GUPUOZIo+VBAgQIECAAAECBAgQIECAAAEC9SBgyNR6QNQFAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECMx8AYG4mX8PnAEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1IOAQFw9IOqCAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGa+gEDczL8HzoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE6kFAIK4eEHVBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAjNfQCBu5t8DZ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC9SAgEFcPiLogQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZkvIBA38++BMyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBehAQiKsHRF0QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwMwXEIib+ffAGRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAPQgIxNUDoi4IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYOYLCMTN/HvgDAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgHgRa1EMfuiBQERg6vDJpggABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECTFOjUvkmelpOqBwGBuHpA1MV3Akt0bfbdjCkCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0ooAhUxsR26EIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoOEEBOIazlbPBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINCIAgJxjYjtUAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQcAICcQ1nq2cCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaEQBgbhGxHYoAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGg4AYG4hrPVMwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0ooBAXCNiOxQBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQINJyAQFzD2eqZAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBpRQCCuEbEdigABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQaTkAgruFs9UyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECjSggENeI2A5FgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAg0nIBDXcLZ6JkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFGFBCIa0RshyJAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBhhMQiGs4Wz0TIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCMKCMQ1IrZDESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDDCQjENZytngkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgEQUE4hoR26EIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoOEEBOIazlbPBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINCIAi0a8VgORYAAAQIECBAgQIAAAQIECBAgQIBAExF464130gXnXpreeO2t1GmBjmnLbXqlH/1419S8+aTfof3LH/+et6vr1HttsXGx326VVW+/9W66564H01xzzZUOOWL/yvKJJx6479F09WX/Sfvuv2fapNcGefWYMWPSddfcku6+4740aODgtNGm66fNem+Y1lpn9cru7779frr7zvvTfXc/lJZdfum0+ZYbF/tvmFq2rHnUOWL4iHTUIb+obF9O9Npyk7THj3ZOP9vv6HLRJK+HHX1QWn3Nnnn5iBEj02MP90133X5vOvGUo9Iii3aZZPtYMHr06HTy8aemr7/+Jp174Z8r28R5XnTBFeml519JXbstksKp91abpo6dOuRtYvsH7n0k3fe/h1K/jz5OvYvz23LbXmnx7t0qffznmpvTPcW1TtzOOv/09ND9j6Vbbrh94lV5vvMindNpf/6/yroZuydj07VX3VA43JdGjfw6bbL5Bvlaeqy8QqV/EwQIECBAgAABAgQIECBAgACBpiQgENeU7oZzIUCA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ECBAgAABAgQIzEyBBq0Qd/3dr6UyELdOz65p3eJHI9AQAuPGfZv+73fHpy6LdM4P6eIY2+24VTrjz+floSWiGtxdRRhu7XXXSPvuv2c+hQMP2TfFsBYDiiFWP/1kQH6AecgR+6ettt08rz/y5z9LMZTpJx9/muen5VdUo4s+osWDzimdU3zrOb59PLljlkORPPzgE/lhad8naoa82HizDablVGxDgAABAgQIECBAgAABAgQIEJiswLffjisq0jertb6sojZu3LhayyeeGT16dLr2qpvStjtskauwTbx+SvPx5cRoPz10vzSuOIcptc+KZzan/eavqfdWm+YqbLFth47t08FH/KQYkvWsSpX/CJnFuZRt/rZtUrfFFs3PeCLMddstd+ehSrt06ZyDeOV28RrPhmKY0VN+e9wkobbq7SaejudF5519cTr0yAPSol27TLx6kvl//fPK/BwovshZtj323iXdfcd96ZILryoXpb+d84c0T/GFz2jx7CiGhe20QIe0SfE86L13P0g3X397OuWEU9OV1/2zVnW52P4/19ycq/6tu8FaMTvNbVrvyd67/rTS5wknHzVN113ZwQQBAgQIECBAgAABAgQIECBAoBEFGiwQ1+eF/jkMt9tWK6Z+RZW4vsV8t85tU9fiRyNQ3wItWrQovkHbKd1z1wPpmaeeTzHURdnGjv02D5UR1dZWX6tnuThXkPvTmb/N8+XwFz1X++6byfEw8x8X/zWvf/yRvpX9pjQR35Yt29TO6Z233subTu6YsXLTXhum++5+KD9cjSFLYhjXaXnIWp6DVwIECBAgQIAAAQIECBAgQIBAXQLNijBcOfxoub4MwjVvPuVBJR6495Fc1f6He+xQ7jpNry8+/0r67013plNPPyXNP3+bPJzp5HYcOnRYOv6oX+XA3c9PPKyyWfQRYbgddtk6RXW6d99+P/39b/9Mf/rD2enk3xybt4svO5ZfeIwFG2+2ftppq73Tffc8NEkg7vp/35Irzm3Sa9q/gBiV2/76/85Nyy63VNp1zx0r5za5iTKQd8LJR+ZKdOV2EWwbNfLrFEPHLtxloXTd1Ten4478v3TJ1eemJZfqnlq1mrvybCr26ZU2TlHF7/xzLklRDa66ol3/YtSDqJgXVlO7f+Xx43V67slNd1yZvzh61+33FUPF/j2HE9ebzvBd9bFNEyBAgAABAgQIECBAgAABAgQaSqDBAnFxwhF+K3+iUlwE4wTiGupWztn9fj3q63T4QSekNvO3Tj8+YM8U1d2efebF9I/im7rRmjWr+cbzuMkMjTFhdfEguO6hM76P7tTPqabXKR0zhkd98P5H01N9ns3fVj76+EO+z6nYhwABAgQIECBAgAABAgQIECBQS6BTpw7pow/61VoWIbRo7Tu0q7W8eiZCdFdffn1asxi6NEJb09PO/PM/8uYRioufqDQX7dqrb0zPP/tS+s1pv8jzX3/9TTr5+FPT8GEj0nnFlxXnm2/evDx+3X/vw3n+8KN/mgNjyyy7VPqwuI4Y9vTo4w7OQ3tWNp4wMffcc6eo6v/2hC8nlus/G/BFrtD2s8N/klq2bFkunurrYw/3Tc8Vz51iRIJf/Py3efsI5o0cOSqH+Hbba8c8SkGsiC9Z/u30c9OPfrxbUcVuy7xt/Pr8s4H5S52HHnVgKsN4x5xwaH4OFMG2ydmWIbiBXwyq9BUTN1z73+yyxdab1Vo+tZlpvSfRT8fiPRM/y62wTHrwvkfzj0Dc1IStJ0CAAAECBAgQIECAAAECBGaGwJS/7jkDZxTV4CIEF5XiqodOnYEu7UpgsgLvv/9R/mZyPFyMYTSWWmaJ4kHmd3nPGE4jHp4+1fe5Sh/fFuG4eOgXQ1Ms2m3RvPy5Z16qrI+Hon/83RnptVfeSO0mPAj+7LPPK+tHFQ85p9Smdk5TO2b0vcbaP8jnHd9yjrbhJuvlV78IECBAgAABAgQIECBAgAABAjMiEBXoP/l4QPpy8JBKN2+89lZ+DjHvhCE7KyuqJp584unU76OP0+577Vy1dNomt9q2VzrgZ/ukVXqulH96rLxC3rHbYl2LoUGXy9PxvObUX/8lvffO++lv5/4hDwFa3fuwr4bn2fLLjTHTrPhftFFFkC7aUYf8Iv3qpNPydPyKynevv/pW6rLIwpVlMXHzDbfn+e2qgmq1NpjMTATh4jqiQl15LQt3WTBvHfMRGvv/7N1nmFXV1QDgDWJv2Bui2EWMPWJvsX4aoxFjjyVq7DXYxUbEgiVij733HrsYjd3Ye0w0iqLGggyogMJ31h7O9c44Q5kZ5KLvfjL3nrLPOfu+h/hjP2uvFe2Vl15LR/Q8IffbdY8d8rHyY+iQoXmzXMQZO5G1L1pdXf1vfO6ZF9Oa3TdOb73xdj4eH2+9+e+8HWMo2+effZFuvemutPmWm6Qxvbuyf/X32N5JjCXGcF6/S6ouqx9n9dirTtokQIAAAQIECBAgQIAAAQIECEx0ge8jhlo5lAh8K9tKS3fKmeC6F99RKjValE4ts8M17lte55tASwW6LDBfnrC96/b70owdZ0gfDhiY+p1xYb7dl4MG5wnPrbb7bbr4givTmX3PS2ustUr6e//H8mrklVf7ZS5XESuFr778hlxKdZHFFswri6P86p777ZLLeERAXaw2jlKoHw38OF1wzmVjHO7YxhTlT8f0zLh5rE6OEh8333BHWnb5pdKss848xmc6SYAAAQIECBAgQIAAAQIECBAYF4F11lszz22cWmQv23aHLXK5zXvuejBtv9NWlcsvufCq1GneufPiw/LgVZffmObtPE9aboWly0Pj/B1zM9Ut5myuvuLGtMpqK1ZKmUb508iqttOu26bBX9alF557uXJJZCZbadUVcha1M045N8+ZvFsskoxgsAjsKudNIljvyENOyPdeotti6b67H8oLKXfabdvKvSLQ69orb0qb9dg4zTDj9JXj47IRczrxV93ifu+9OyBXLojjkbWu5/69cha5yAAXpUnLFs/rPF+nHOx3TeE5wwzTpTnmnD3PU0WfFbsvl7sus/wv8pzVGaeel36/y1Y5ePGyi67J91ys6yLl7dLtt9ydtzfdfKPKsXHdGJd30u0XXdPtRUa/CCiMzHUP3Ptwzoa3xXiWzB3XMelHgAABAgQIECBAgAABAgQIEGitQJsExEWAWwS+RcBbZIWLFkFx8VfuVwfDVfeN8xEspxFojcBUU02ZDjly/xQTtYcffFwOjotJwCi/8eEHA/Mk5dbbb5G+/vrrHNR2yw13pplm6pgOPWr/SgmLo4/vmU498ax0/tn1K17n79I5nX/J6aljx/oyIQccsle6vJh0PGifI/P9u6+yQnrysWeKcqzfj7z96JW8cWRcxjS2Z8Z91vrVajkgLjLfaQQIECBAgAABAgQIECBAgACBthCYY87ZUu+Tj0y9j+mbA9DinlFuc9vf98i3HzVqVBEEdXdaatlulYC414sMcpH1LOZgqudAmhvP2DKIVc5Xza3cf0//fLuY42ncLrqyXx5LlAuNhYp/u+P+3CXKt+6y+/aV7rH4cdc9fp/u+9tD6YKzL83Hd9h5q6Jk6XqVPuW1m2+xceVYUxvlvE/VEJvqNjpH3fenIpAvSqjGX8wlVbfVi4Wax514WDqxb690yp/PSn2OPyOfjsWYB/TcM8WcU7TwCevrr7m1Upp1wYXmT4f3OijPO0WfKC97/dW35HdXnTUuzjXVKuZNnSyOVc5X/eBjilK2p510dq60UF62xz47p0UWW6jc9U2AAAECBAgQIECAAAECBAgQqCmBdnV1daNaM6IIaIuSqJENLgLgYjtaGeTW1H4Ex5XBcnG+Ontca8YS19YNHvqD8getveekdv1ng0alWTpWzVpNaj+gleP94otBacYZZ2x2YjZKb8QK5LJ8RePHDRs2PA0rJhObWx0cZSiiBGtlgrDxDZrYH9uYxvTMKOkaE6O333dNsWJ4/FYsNzEUhwgQIECAAAECBAgQIECAAAECDQSibOp0009XZKpvuHb222+/zZn0x2cOpMGNJ+DOyJGj0ueff5Gmm27aSnBYU4+LeZyOM8U8UfumTtfEsa+//iZ9XQTOjWm+6ZuiT8xpTVv83onVYv4qMuHNPPNMzc67TayxeS4BAgQIECBAgAABAgQIECDwvcDPPW4oJBrOcn1vM85bZea38oLYjwxwp1/6ZA6Si4C5OBbfkUkuvsv2/ujtxvcoz/sm0BKByPw2pjbZZJM1GwwX10055RT5r7l7NBdI11z/OD62MTX1zJhgfO2VN9L5/S7NZUMEw41J2DkCBAgQIECAAAECBAgQIECgpQIRiNVU69Ch1VOHTd22TY5FhrqyROqYbtiSeZwx3W9CnJt66qlS/I2pTTWW82O6tq3O1c9fzdxWt3MfAgQIECBAgAABAgQIECBAgMAEE2h1hrgYWVkytRxlZIuLFoFx1S0C38qAufJ4mVmu3G/ttwxxKYn0bO2/otq4/sbrbk/9Tr8gLbLogqlvv95p+mKltkaAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgOQFxQ22QIS5wo/xp2eYdHfRW7pdBcREIV5ZRjT6RHa5x3/Ia3wQIpLTRJuumVVZb8WdfAti/BQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLjKtBmdQ+qg+Li4VEaNYLhqrPFleVTy0xx4zpI/Qj8HAWmmWbqFH8aAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLjJtBmAXHl426457VyM5dHLQPlIhguzkUwXLQ4Xm5XLrBBgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaKNC+hdc1edkTRUa4aE0FupXH4jv+qgPnmryZgwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYDwE2jQgLkqkRrBbZH+Lv8gKF3/RyvKpcXze0VniynPjMV5dCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAkwJtGhAXwXBlkNv7owPh4li0MZ1rcmQOEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB8RBoV1dXN2o8+o+xawTDVZdC7bFB10r51DgXJVXLgLnuo7PIjfGGLThZN3hommvuOVpw5U/nks8GjUqzdGz30/lBfgkBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAmMVEDeUUpsGxJXiEfRWZoYrj5XfYzpX9mnNt4C4lPzDbs2/INcSIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQmDQFxA2l1KYlU8t/Bs0Fw8X5MZ0rr/dNgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTGV2CCBMSN7yD0J0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrRUQENdaQdcTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQE0ICIiriddgEAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQWgEBca0VdD0BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI1ISAgLiaeA0GQYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKtFRAQ11pB1xMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBATQgIiKuJ12AQBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINBagQ6tvYHrCVQLvDNgVPWubQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQI1J9ClU7uaG5MBtY2AgLi2cXSX0QL+Y+GfAgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECE0tAydSJJe+5BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINCmAgLi2pTTzQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgYgkIiJtY8p5LgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAm0qICCuTTndjAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQmloCAuIkl77kECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0KYCAuLalNPNCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGBiCQiIm1jynkuAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECbSogIK5NOd2MAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCaWgIC4iSXvuQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQpgIC4tqU080IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYGIJCIibWPKeS4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJtKiAgrk053YwAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEJpaAgLiJJe+5BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINCmAgLi2pTTzQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgYgkIiJtY8p5LgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAm0q0KHx3T74pH165e326b2B7dOgIe3SyJGNe/y4++2LkL2O041KnecambotNDLNM/tEHtCP+/M9jQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTGUaBBQNy9j3dIL7w52The+uN0i4C8zwe3K/4my2NbetHv0vorf/vjPNxTCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCSEaiUTL3+vslrLhiuKcUI2IuxagQaC9x/T//02itvND48Qfbffee9dNft96URI0ZMkPu7KQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC4y+QM8RFZrh3PqjExo3/XX7kK2KsMWaZ4n5k+Bp/3Oknn5M2+c0GqWu3xSb4SF96/tV02slnp9XWXClNPrkAzQkO7gEECBAgQIAAAQIECBAgQIBAmwu89cbb6bx+l6Q3XnsrzTLrzGn9jdZJ2+ywRWrfvul5woEffpwuvuDK9OxTz6fppp82bbzp+mnLbTZL7dq1azC2Dz/4KNUvXHwzdV9l+bTZFhtXzv/7X++kC8+7PL30/Cup07xzp3XWWyOtu8FaaeZZZsp9YvHh9Vffmu6564H02aefp9XXWiWtve5q6Zfdl0vfffdd2u33+1Xu1Xhjz/3+kOLZt954Z+NTeX/OuedMvU8+snLu66+/SXfddm969eU30pRTTZkOPWr/yrkvvhiUzjrtgvT8sy/lYyutukLac98/5N9ddrr1prvSfXf3T+/+57/pF8t0S/scsFuap9Nc5ekG3w898Gi66tLr0vY7bZXWXGfVyrn4Tf985oXi9z6YVl1jpbT2r1arnIuNR/o/lq6+4sb03rsD8jPWWme1tNavVk1TTDFF7vfF54PSRedfkZ564p95f93110zrFH8LLjR/3o+PY484Kb333/cr+7ExdzHO4/sckU7581/y+29wcvROvJttduiR98bntzZ1L8cIECBAgAABAgQIECBAgAABAj+mQIcPPmk/SWSGa4wSmeK6LTQyzTN7UVNVI0CAAAECBAgQIECAAAECBAgQIEBgnAUi4Gu3HfdPM83UMW230+/Sc8+8mP5aBKpNPsXk6XdFkFvjNqRuaOq5/9E5SC2CpCIQ69yzLk7fjRyZttl+i0r3p554NgdgTTnllGmNtVdJCy28QOXcRwM/Trtsv0+ae5450w47b5UGf1mX7/HgfX9P511yRhGI1y6dd9Yl6abrb0/rbbh26vaL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AAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGWCrQ6Q1xzD45yqdUlU6u3m7vGcQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0FKBNssQFyVSu3RKaf7R5U/fLQLiHnrqu7Tz5vWPiHKqUV419hv3bengXUeAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBEqBVgfERea3/k/Xl0ktbxrHIhiuuVZdSjX6yh7XnJTjBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDCuAq0OiIsHRfDbOwNGVp7ZXIBbHD/qrBE5U1zZubm+5XnfBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgXATaJCAuHiSwbVy49SFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBCSXQZgFx4zrAnTfv0CBDXJRbHVN51XG9r34ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg8PMW+FED4tZecbIcDBfZ5N4t/qKt9cv2udyqDHM/73+Ifj0BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaK/CjBMR1maddisxwZev/1He5xGocj4A4jQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItFZggkejRfBbtAh+iz+NAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhMCIHv07ZNiLsX94xSqEedNSLfPUqmRka4Lp0iDm/k6O8J9GC3JUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGflcAED4ir1nxnwMgcEBdBcdWlUiNoTiNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAq0R+HED4orAt/5Pj2ww3giS0wgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQGsFftSAuBjsQ09919oxu54AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECPxA4EcPiPvBCBz4SQm8M0D525/UC/VjCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQI/QYEundr9BH+VnxQCAuL8O2hTAf+xaFNONyNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYDwE2o9HX10JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDNCgiIq9lXY2AECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMD4CAuLGR0tfAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKhZAQFxNftqDIwAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIExkdAQNz4aOlLgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAjUr0L79JBwSNymPvWb/RRgYAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEJlGB9h2nGzWJDj2lSXnskyy6gRMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBGBdp3nmtkjQ5t7MOalMc+9l+nBwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMj0D7bgtNugFxk/LYx+cl6UuAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECYxdoP8/sI9PSi3439p411iPGHGPXCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBACLSPj/VX/jZ1mWfSCS6LscaYNQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUArkgLjY2XK9EZNEprjIDBdj1QgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQLVAh+qdyLrWbaGR6ZW326f3BrZPg4a0SyMncuK49kXIXsfpRqXOc43MY1MmtfqN2SZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBUqBBQFwcjIAzQWclj28C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L5oIB4cNG54iY9qYxhnZ8uK3lkFqE2KYkfU/5pGqF0w2fk6UTY1seB06NHzPjfvZJ0CAAAECBAgQIECAAAECBCZtgZ973FC8PbMfk/a/YaNvQmCWRqtsm+iSg8xqPRguxt3cpPAVl1yb+j/waC55OvkUHXLGu+hflsOI7aZaBLnNPMtMTZ2qHJtp5o6V7cYbEZw3ponVxv3tEyBAgAABAgQIECBAgAABArUv0NxcwIQMnJpu+mnHCBNzEI0zqY3xgol0csopp0hTTtkw41vjoTRXkrVxv9bsTzHFFCn+xtTGNic0pmudI0CAAAECBAgQIECAAAECBAhMSgIC4ialt2WsBEYLbLn1ZumjgZ+kIw85IR+JMqs9j9gvLbfC0owIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI/GwFlEz9Cb56qQ9/gi+1mZ8U5UOGDBmaZphh+mZ6OEyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIPBzERA3lFL7n8vL9jsJ/BQFonyIYLif4pv1mwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBFoiICCuJWquIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGaExAQV3OvxIAIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoCUCAuJaouYaAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEKg5AQFxNfdKDIgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEWiIgIK4laq4hQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZoTEBBXc6/EgAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgJQIdWnJRc9cM+GhweuKFAfl0pzlnSCst3alB1xvuea3Zcw062iFAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAuMp0KYBcWXAW4whguPmLYLiIjCu3I9j5faTReBcjw26Vs7nEz4IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEALBdosIK7MDNdckFsExh2wY/c8zAiMi+C5uCb6awQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoLUC7Vt7g/L6MvtbmRGuPN7U97j0aeo6xwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQHMCbRYQ19wDHCdAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAj+GQJsExEX508gQ133pTuM85sgSF9eUpVbH+UIdCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAEwKtDoiLoLb4iwC3lUYHxEWAXNnKYLnYj+C3MgCuxwZdcwDdk8WxuF4jQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKtEWh1QFz58AiIi9Y4uC2Ov99MwFvjvuW9fBMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgfEVaFdXVzdqfC9q3D+yvkWmtwh+i8xv49Jacs243Ldu8NA019xzjEvXn2yfzwaNSrN0bDdRft87A1r9z2mijNtDCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEfj4CXTpNnNiaCS08MeOGJvRvG9f7dxjXjmPqF6VSI9vb+GR8G98AujE937naEfip/seidoSNhAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB5gTarGRqcw9wnAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI/BgCbR4QNy5Z4so+UWJVI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECbSHQJiVTYyBRNvWGe17Lf+XAemzQNZVBbxEEF+erW1yjESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBthBos4C4CHw7YMfu6YkXBqSmMsDF+e5FAFx5TjBcW7w+9yBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBUqDNAuLKG44p0G1M58rrfRMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZYItG/JRa4hQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1JiAgrtbeiPEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQIsEBMS1iM1FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFBrAgLiau2NGA8BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEhAQFyL2FxEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABArUmICCu1t6I8RAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAiwQExLWIzUUECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUGsCAuJq7Y0YDwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0SEBAXIvYXESAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECtSYgIK7W3ojxECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECLBATEtYjNRQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQawIC4mrtjRgPAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRIQEBci9hcRIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1JiAgrtbeiPEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQIsEBMS1iM1FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFBrAgLiau2NGA8BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEhAQFyL2FxEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABArUmICCu1t6I8RAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAiwQExLWIzUUECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUGsCAuJq7Y0YDwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0SEBAXIvYXESAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECtSYgIK7W3ojxECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECLBATEtYjNRQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQawIC4mrtjRgPAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRIQEBci9hcRIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1JiAgrtbeiPEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQIsEBMS1iM1FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFBrAgLiau2NGA8BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEhAQFyL2FxEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABArUmICCu1t6I8RAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAiwQExLWIzUUECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUGsCAuJq7Y0YDwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0SEBAXIvYXESAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECtSYgIK7W3ojxECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgECLBATEtYjNRQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQawIC4mrtjRgPAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRIQEBci9hcRIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1JiAgrtbeiPEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQIsEBMS1iM1FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFBrAgLiau2NGA8BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEhAQFyL2FxEgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABArUmMMED4gZ8NDjFn0aAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCakQIcJcfMnXhiQg+AaB8J1mnOGFH8rLd1pQjzWPQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEDgZyzQphniIgDuhnteS08WAXHRIvit++jgt/I7zkWfxsFyP+N34KcTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQBsItKurqxvVBvfJAW4R6BYtgt/KLHCRLS5a9X4ZMNdjg645aC53aKOPusFD01xzz9FGd5s0b/PZoFFplo7tJs3BGzUBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAi0SEDeUUptliCsD3yLIrQx+i7cS2433o0+08pq844MAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRCoE0C4iKwLUqgRma4KJM6tlaWUo1rBMWNTct5AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBgXgTYJiIvAtghyq84E19TDo1+UVY3v6BvXxLZGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaK9CmAXFjGkx1MFx1FjkBcWNSc44AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIExlWg1QFxZUDbk0XZ1LL8afldDqIMhov9Hht0zYejTxkYV96j7O+bAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMr0CrA+LKB3YvSqBGi+C2CI6L0qjlfrkdwXBlEFyci34aAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoC4EOrb1JGeAWgXBl9rcIjiuD4srsb42D4VYaHUAX/cp7tHYsridAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBn69AqwPigq5xQFt1sFucbxwMF8eiRbBc42vrz/gkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLjJ9AmJVMjqC2C256oKoEaQXGRKa65YLjoKyBu/F6W3gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQvECbBMRF8FsExUX507JEajyyPN748dGnLJVaZpNr3Mc+AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYH4E2CYiLB5aBbTfc81qDTHGRCa46c1xsR5/qa/KODwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0AqBdnV1daNacX2DS8uyqWUp1DJrXJROLTPHleeayx7X4IYt2KkbPDTNNfccLbjyp3PJZ4NGpVk6tvvp/CC/hAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBsQqIG0qpTQPiSvHIAheBb2UQXHk8AuTir8wmVx5vy28BcSn5h92W/6LciwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCkISBuKKUOE+JVVQe8lUFxEQinESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBCSUwQQLiqgcrEK5awzYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQITCiB9hPqxu5LgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgR+TAEBcT+mtmcRIECAAAECBAgQIPD/7d2xbVxHFIVhgXDs2AHZgCOFVAduwLlbcF1qQB1IoSI1IAaOXYE5BAdYLJYSj9/O6AD6FhCo5V69d/VN+uMtAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLLBARxy2hdmAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgR2Cgjidmq7FwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgsExDELaN1YQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYKSCI26ntXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwTEAQt4zWhQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgp4Agbqe2exEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAMgFB3DJaFyZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBnQKCuJ3a7kWAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECywQEcctoXZgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEdgoI4nZquxcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQILBMQxC2jdWECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2CkgiNup7V4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsExAELeM1oUJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYKeAIG6ntnsRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwDIBQdwyWhcmQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZ0Cgrid2u5FgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAssEBHHLaF2YAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBHYKCOJ2arsXAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECCwTEMQto3VhAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENgpIIjbqe1eBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILBMQBC3jNaFCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCngCBup7Z7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMAyAUHcMloXJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGdAoK4ndruRYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLLBARxy2hdmAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgR2Cgjidmq7FwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgsExDELaN1YQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYKSCI26ntXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwTEAQt4zWhQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgp4Agbqe2exEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAMgFB3DJaFyZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBnQKCuJ3a7kWAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECywQEcctoXZgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEdgoI4nZquxcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQILBMQxC2jdWECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2CkgiNup7V4ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsEzghwVxD//8u+w/5cIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg8PMJ/JAgbsRw7z98efPx88PPJ+5/TIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJLBH45etX5pLevLzzx7e63X9/cPv45fY3348+n5yDu3dvb04/9nQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIxAKHg7jxpLdvve7++P3p4xnOzdkRwY1/K4qbIn4SIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwBGBQ0HcjNzuH+O28SS4S6/xJLj5FamXPh+/G1HcpSfJvTTv9wQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4FzgUBB3erHzr0U9/2xEc+ev+XS4+RWq5597T4AAAQIECBAgQIAAAQIECBAgQIDmXgZvAAAaGklEQVQAAQIECBAgQIAAAQIECBAgQIAAAQIEXitwtSDu9IbziXAjghtfjTpe8+ec+/j4VLjxGjHcn89fqzo/85MAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECKQCN+k/+N78jOHG3EtfozpmxtPhxHDf0/Q5AQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLxW4KpPiDuN4eZXqI7fzdf83Qzh5vv5uZ8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQOD/Clw1iJtfgzqWOY3jTpf7+6/7p7diuFMVfydAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBowJXDeJG5DafCDf+Lno7ejz+PQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi8VuCqQdy7t7dP9/30+eEpjBvvvxXFjXjuW5+/9j9hjgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI3FybYERw989h3NfH4O2l1/xK1dOvWX1p1u8JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMD3BK76hLh5sxHF3T1+ZaoXAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYJXAoiJtfdzq/InXX0u5DgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTOBQ4FceNi4+tRx9efnr7O38/PZkB3+n48Tc6LAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgcFTgcxJ0HbSOGe//hy8W9RhB3Pn9x0C8JECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAocDiIO7/fiN7GU+MuvcRwl1T8jgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSuIXD1IG4sJXy7xtG4BgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgkAjfJsFkCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAkIIiLuAwTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQKuAIK71ZOxFgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApGAIC7iMkyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECrQKCuNaTsRcBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIRAKCuIjLMAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0CgjiWk/GXgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAQCQjiIi7DBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAqIIhrPRl7ESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAk8B+LnGlaFZbbmAAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "id": "217279f8-6af1-4209-b0ec-3d3d829ceed9", + "metadata": {}, + "source": [ + "![image.png](attachment:66422f79-9b46-4e07-9796-c1b350c26c9c.png)" + ] + }, + { + "cell_type": "markdown", + "id": "844edc05-0b6a-4e84-9213-1d3cbf6f833e", + "metadata": {}, + "source": [ + "## Use the function for model serving" + ] + }, + { + "cell_type": "markdown", + "id": "40182a6f-fc46-4a33-a7f5-7ee8ee171966", + "metadata": {}, + "source": [ + "### Create the server and serving function\n", + "\n", + "Create a serving function that uses the model from the previous run and serves it using MLRun.
\n", + "We will create a mock server to test the model in a local environment." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f5fe910b-e177-4af7-84de-41a571d1774c", + "metadata": {}, + "outputs": [], + "source": [ + "serving_func = project.set_function(\n", + " func=\"function.yaml\",\n", + " name=\"example-xgb-server\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ddbfd48f-a90e-4fe6-9caa-ddffeacf63d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Add the model\n", + "serving_func.add_model(\n", + " \"mlflow_xgb_model\",\n", + " class_name=\"MLFlowModelServer\",\n", + " model_path=train_run.outputs[\"model\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "2298d111-2f53-4b84-be9e-e4e8a228dcc4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-27 15:37:31,627 [info] model mlflow_xgb_model was loaded\n", + "> 2024-03-27 15:37:31,628 [info] Loaded ['mlflow_xgb_model']\n" + ] + } + ], + "source": [ + "# Create a mock server\n", + "server = serving_func.to_mock_server()" + ] + }, + { + "cell_type": "markdown", + "id": "f54d7c06-4972-4881-9bc9-fba7db0adbe4", + "metadata": {}, + "source": [ + "### Test the model " + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "4f256490-f225-4bd6-ac8a-5fc12a0f335d", + "metadata": {}, + "outputs": [], + "source": [ + "# An example taken randomly \n", + "result = server.test(\"/v2/models/mlflow_xgb_model/predict\", {\"inputs\":[{\"age\": 20, \"gender\": 0}]})" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "47839f4b-bb2d-4341-99c5-e34fa31270c9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'id': '43a61d06f2694fa695bdd6561b487131',\n", + " 'model_name': 'mlflow_xgb_model',\n", + " 'outputs': [[0.9242361187934875, 0.0418272465467453, 0.033936627209186554]]}" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Look at the result, it shows the probability of the given example to be each of the \n", + "# irises featured in the dataset\n", + "result" + ] + }, + { + "cell_type": "markdown", + "id": "d4fc6c73-0963-4814-bd5f-2d27b464823e", + "metadata": {}, + "source": [ + "We predicted that a 20 year old female would like pop!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.py b/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.py new file mode 100644 index 00000000..fb6124be --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/mlflow_utils.py @@ -0,0 +1,45 @@ +import zipfile +from typing import Any, Dict +import mlflow +from mlrun.serving.v2_serving import V2ModelServer +import pandas as pd + + +class MLFlowModelServer(V2ModelServer): + """ + MLFlow tracker Model serving class, inheriting the V2ModelServer class for being initialized automatically by the model + server and be able to run locally as part of a nuclio serverless function, or as part of a real-time pipeline. + """ + + def load(self): + """ + loads a model that was logged by the MLFlow tracker model + """ + # Unzip the model dir and then use mlflow's load function + model_file, _ = self.get_model(".zip") + model_path_unzip = model_file.replace(".zip", "") + + with zipfile.ZipFile(model_file, "r") as zip_ref: + zip_ref.extractall(model_path_unzip) + + self.model = mlflow.pyfunc.load_model(model_path_unzip) + + def predict(self, request: Dict[str, Any]) -> list: + """ + Infer the inputs through the model. The inferred data will + be read from the "inputs" key of the request. + + :param request: The request to the model using xgboost's predict. + The input to the model will be read from the "inputs" key. + + :return: The model's prediction on the given input. + """ + + # Get the inputs and set to accepted type: + inputs = pd.DataFrame(request["inputs"]) + + # Predict using the model's predict function: + predictions = self.model.predict(inputs) + + # Return as list: + return predictions.tolist() diff --git a/functions/master/mlflow_utils/1.1.0/src/requirements.txt b/functions/master/mlflow_utils/1.1.0/src/requirements.txt new file mode 100644 index 00000000..2a40b1a8 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/requirements.txt @@ -0,0 +1,3 @@ +mlflow==2.20.2 +lightgbm +xgboost diff --git a/functions/master/mlflow_utils/1.1.0/src/test_mlflow_utils.py b/functions/master/mlflow_utils/1.1.0/src/test_mlflow_utils.py new file mode 100644 index 00000000..70d6ce03 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/src/test_mlflow_utils.py @@ -0,0 +1,179 @@ +# Copyright 2018 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import tempfile + +import lightgbm as lgb +import mlflow +import mlflow.environment_variables +import mlflow.xgboost +import pytest +import xgboost as xgb +from sklearn import datasets +from sklearn.metrics import accuracy_score, log_loss +from sklearn.model_selection import train_test_split + +import os +# os.environ["MLRUN_IGNORE_ENV_FILE"] = "True" #TODO remove before push + +import mlrun +import mlrun.launcher.local +# Important: +# unlike mlconf which resets back to default after each test run, the mlflow configurations +# and env vars don't, so at the end of each test we need to redo anything we set in that test. +# what we cover in these tests: logging "regular" runs with, experiment name, run id and context +# name (last two using mlconf), failing run mid-way, and a run with no handler. +# we also test here importing of runs, artifacts and models from a previous run. + +# simple mlflow example of lgb logging +def lgb_run(): + # prepare train and test data + iris = datasets.load_iris() + X = iris.data + y = iris.target + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42 + ) + + # enable auto logging + mlflow.lightgbm.autolog() + + train_set = lgb.Dataset(X_train, label=y_train) + + with mlflow.start_run(): + # train model + params = { + "objective": "multiclass", + "num_class": 3, + "learning_rate": 0.1, + "metric": "multi_logloss", + "colsample_bytree": 1.0, + "subsample": 1.0, + "seed": 42, + } + # model and training data are being logged automatically + model = lgb.train( + params, + train_set, + num_boost_round=10, + valid_sets=[train_set], + valid_names=["train"], + ) + + # evaluate model + y_proba = model.predict(X_test) + y_pred = y_proba.argmax(axis=1) + loss = log_loss(y_test, y_proba) + acc = accuracy_score(y_test, y_pred) + + # log metrics + mlflow.log_metrics({"log_loss": loss, "accuracy": acc}) + + +# simple mlflow example of xgb logging +def xgb_run(): + # prepare train and test data + iris = datasets.load_iris() + x = iris.data + y = iris.target + x_train, x_test, y_train, y_test = train_test_split( + x, y, test_size=0.2, random_state=42 + ) + + # enable auto logging + mlflow.xgboost.autolog() + + dtrain = xgb.DMatrix(x_train, label=y_train) + dtest = xgb.DMatrix(x_test, label=y_test) + + with mlflow.start_run(): + # train model + params = { + "objective": "multi:softprob", + "num_class": 3, + "learning_rate": 0.3, + "eval_metric": "mlogloss", + "colsample_bytree": 1.0, + "subsample": 1.0, + "seed": 42, + } + # model and training data are being logged automatically + model = xgb.train(params, dtrain, evals=[(dtrain, "train")]) + # evaluate model + y_proba = model.predict(dtest) + y_pred = y_proba.argmax(axis=1) + loss = log_loss(y_test, y_proba) + acc = accuracy_score(y_test, y_pred) + # log metrics + mlflow.log_metrics({"log_loss": loss, "accuracy": acc}) + + +@pytest.mark.parametrize("handler", ["xgb_run", "lgb_run"]) +def test_track_run_with_experiment_name(handler): + """ + This test is for tracking a run logged by mlflow into mlrun while it's running using the experiment name. + first activate the tracking option in mlconf, then we name the mlflow experiment, + then we run some code that is being logged by mlflow using mlrun, + and finally compare the mlrun we tracked with the original mlflow run using the validate func + """ + # Enable general tracking + mlrun.mlconf.external_platform_tracking.enabled = True + # Set the mlflow experiment name + mlflow.environment_variables.MLFLOW_EXPERIMENT_NAME.set(f"{handler}_test_track") + with tempfile.TemporaryDirectory() as test_directory: + mlflow.set_tracking_uri(test_directory) # Tell mlflow where to save logged data + + # Create a project for this tester: + project = mlrun.get_or_create_project(name="default", context=test_directory) + + # Create a MLRun function using the tester source file (all the functions must be located in it): + func = project.set_function( + func=__file__, + name=f"{handler}-test", + kind="job", + image="mlrun/mlrun", + requirements=["mlflow"], + ) + # mlflow creates a dir to log the run, this makes it in the tmpdir we create + trainer_run = func.run( + local=True, + handler=handler, + artifact_path=test_directory, + ) + + serving_func = project.set_function( + func=os.path.abspath("function.yaml"), + name=f"{handler}-server", + ) + model_name = f"{handler}-model" + # Add the model + upper_handler = handler.replace("_", "-") + model_path = test_directory + f"/{upper_handler}-test-{upper_handler}/0/model/" + serving_func.add_model( + model_name, + class_name="MLFlowModelServer", + model_path=model_path, + ) + + # Create a mock server + server = serving_func.to_mock_server() + + # An example taken randomly + result = server.test(f"/v2/models/{model_name}/predict", {"inputs": [[5.1, 3.5, 1.4, 0.2]]}) + print(result) + assert result + # unset mlflow experiment name to default + mlflow.environment_variables.MLFLOW_EXPERIMENT_NAME.unset() + + diff --git a/functions/master/mlflow_utils/1.1.0/static/documentation.html b/functions/master/mlflow_utils/1.1.0/static/documentation.html new file mode 100644 index 00000000..ff93711e --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/documentation.html @@ -0,0 +1,271 @@ + + + + + + + +mlflow_utils package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

mlflow_utils package#

+
+

Submodules#

+
+
+

mlflow_utils.mlflow_utils module#

+
+
+class mlflow_utils.mlflow_utils.MLFlowModelServer(context=None, name: str | None = None, model_path: str | None = None, model=None, protocol=None, input_path: str | None = None, result_path: str | None = None, shard_by_endpoint: bool | None = None, **kwargs)[source]#
+

Bases: V2ModelServer

+

MLFlow tracker Model serving class, inheriting the V2ModelServer class for being initialized automatically by the model +server and be able to run locally as part of a nuclio serverless function, or as part of a real-time pipeline.

+
+
+load()[source]#
+

loads a model that was logged by the MLFlow tracker model

+
+
+
+predict(request: Dict[str, Any]) list[source]#
+

Infer the inputs through the model. The inferred data will +be read from the “inputs” key of the request.

+
+
Parameters:
+

request – The request to the model using xgboost’s predict. +The input to the model will be read from the “inputs” key.

+
+
Returns:
+

The model’s prediction on the given input.

+
+
+
+
+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/1.1.0/static/example.html b/functions/master/mlflow_utils/1.1.0/static/example.html new file mode 100644 index 00000000..72f59a9b --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/example.html @@ -0,0 +1,1194 @@ + + + + + + + +MLflow tracker demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

MLflow tracker demo#

+

This demo demonstrates how to seamlessly integrate and transfer logs from MLflow to MLRun, +creating a unified and powerful platform for your machine learning experiments.

+

You can combine MLflow and MLRun for a comprehensive solution for managing, tracking, and deploying machine learning models.

+

This notebook guides you through the process of:

+
    +
  1. Setting up the integration between MLflow and MLRun.

  2. +
  3. Extracting data, metrics, and artifacts from MLflow experiments.

  4. +
  5. Creating MLRun artifacts and projects to organize and manage the transferred data.

  6. +
  7. Leveraging MLRun’s capabilities for model deployment and data processing.

  8. +
+

By the end of this demo, you will have a understanding of how to establish a smooth flow of data between MLflow and MLRun.

+
+

MLRun installation and configuration#

+

Before running this notebook make sure the mlrun package is installed (pip install mlrun) and that you have configured the access to MLRun service.

+
+
+
# Install MLRun and scikit-learn if not already installed. Run this only once. Restart the notebook after the install!
+# %pip install mlrun scikit-learn~=1.3.0
+
+
+
+
+

Then you can import the necessary packages.

+
+
+
import pandas as pd
+import os
+import mlrun
+from mlrun.datastore.targets import ParquetTarget
+import mlrun.feature_store as fstore
+
+
+
+
+

Create a project for this demo:

+
+
+
# Create a project for this demo:
+project = mlrun.get_or_create_project(name="mlflow-tracking-example", context="./")
+
+
+
+
+
> 2024-03-27 15:34:40,940 [info] Project loaded successfully: {'project_name': 'mlflow-tracking-example-guy'}
+
+
+
+
+

Set all the necessary environment variables for the Databricks cluster:

+
+
+
DATABRICKS_HOST="add your host"
+DATABRICKS_TOKEN="add your token"
+DATABRICKS_CLUSTER_ID="add your cluster id"
+
+
+
+
+
+
+
os.environ["DATABRICKS_HOST"] = DATABRICKS_HOST
+os.environ["DATABRICKS_TOKEN"] = DATABRICKS_TOKEN
+
+
+
+
+
+
+
# Set the Databricks environment variables
+job_env = {
+    "DATABRICKS_HOST": DATABRICKS_HOST,
+    "DATABRICKS_CLUSTER_ID": DATABRICKS_CLUSTER_ID
+}
+secrets = {"DATABRICKS_TOKEN": DATABRICKS_TOKEN}
+
+# Set the secrets in the project
+project.set_secrets(secrets)
+
+
+
+
+
+
+

Create a feature set and ingest data#

+

This is a short example of how to create a feature set about music preferences.

+
+
+
# create df
+columns = ["id", "name", "age", "gender", "favorite_music_type"]
+data = [
+    (1, "Alice", 20, "f", "Pop"),
+    (2, "Bob", 30, "m", "Rock"),
+    (3, "Charlie", 25, "m", "Pop"),
+    (4, "David", 40, "m", "Classical"),
+    (5, "Eva", 18, "f", "Pop"),
+    (6, "Frank", 32, "m", "Rock"),
+    (7, "Grace", 28, "f", "Pop"),
+    (8, "Henry", 45, "m", "Classical"),
+    (9, "Ivy", 22, "f", "Pop"),
+    (10, "Jack", 38, "m", "Classical"),
+    (11, "Karen", 27, "f", "Pop"),
+    (12, "Liam", 19, "m", "Pop"),
+    (13, "Mia", 27, "f", "Rock"),
+    (14, "Nora", 31, "f", "Rock"),
+    (15, "Oliver", 29, "m", "Pop"),
+    (16, "Ben", 38, "m", "Pop"),
+    (17, "Alicia", 20, "f", "Pop"),
+    (18, "Bobby", 30, "m", "Rock"),
+    (19, "Charlien", 22, "f", "Pop"),
+    (20, "Davide", 40, "m", "Classical"),
+    (21, "Evans", 19, "m", "Pop"),
+    (22, "Franklin", 34, "m", "Rock"),
+    (23, "Grace", 22, "f", "Pop"),
+    (24, "Henrik", 48, "m", "Classical"),
+    (25, "eevee", 29, "f", "Pop"),
+    (26, "Jack", 75, "m", "Classical"),
+    (27, "Karen", 26, "f", "Pop"),
+    (28, "Lian", 21, "f", "Pop"),
+    (29, "kia", 27, "f", "Rock"),
+    (30, "Novak", 30, "m", "Rock"),
+    (31, "Olivia", 29, "f", "Pop"),
+    (32, "Benjamin", 18, "m", "Pop")
+]
+df = pd.DataFrame(data, columns=columns)
+
+
+
+
+

Transfer the data to DataBricks.

+
+
+
# Where to save the data in DataBricks
+target_path = f"dbfs:///demos/mlrun_databricks_demo/music.parquet"
+output_path = f"dbfs:///demos/mlrun_databricks_demo/music_output_new.parquet"
+
+targets = [ParquetTarget(path=target_path)]
+
+# Create a feature set and ingest the data
+fset = fstore.FeatureSet(name="music_fset", entities=[fstore.Entity("name")])
+fstore.ingest(fset, df, targets=targets, overwrite=True)
+
+# Get the target path and check it
+dbfs_data_path = fset.get_target_path()
+dbfs_data_path
+
+
+
+
+
'dbfs:///demos/mlrun_databricks_demo/1711553684480_33/music.parquet'
+
+
+
+
+

We can look and see how how our data is logged in the DataBricks cluster: +(only top 20 rows)

+

image.png

+
+
+

Create a data processing function#

+

The following code demonstrates how to create a simple data processing function using MLRun. +The function will process the data and show some statistics.

+
+
+
%%writefile process_data.py
+
+
+#  Here is an example of Spark processing.
+from pyspark.sql import SparkSession
+from pyspark.sql.functions import avg, min, max
+import pandas as pd
+import json
+import fsspec
+
+def process_data(data_path: str, data_output_path: str):
+    spark = SparkSession.builder.appName("MusicDemo").getOrCreate()
+    spark_df = spark.read.parquet(data_path, header=True)
+    spark_df = spark_df.drop("name", "id")
+    
+    music_stats = spark_df.groupBy("favorite_music_type").agg(
+        avg("age").alias("avg_age"),
+        min("age").alias("min_age"),
+        max("age").alias("max_age")
+    )
+    music_stats.show()
+    pandas_df = spark_df.toPandas()
+    pandas_df.to_parquet(data_output_path)
+    # spark_df.write.mode("overwrite").parquet(data_output_path)
+
+    return {"music_data": data_output_path}
+
+
+
+
+
+
+
process_data_function = project.set_function(
+    func="./zeev-demos/mlflow-databricks/process_data.py",
+    name="process-data",
+    kind="databricks",
+    image="mlrun/mlrun",
+)
+                                
+
+
+
+
+

Set all parameters necessary for the function and run it.

+
+
+
for name, val in job_env.items():
+    process_data_function.spec.env.append({"name": name, "value": val})
+params = {
+    "task_parameters": {"timeout_minutes": 15},
+    "data_path": dbfs_data_path,
+    "data_output_path": output_path.replace("dbfs://", "/dbfs"),
+}
+run = process_data_function.run(
+    handler="process_data",
+    params=params,
+)
+
+
+
+
+
> 2024-03-27 15:34:45,422 [info] Storing function: {'name': 'process-data-process-data', 'uid': 'a9c770f8377046bda3061e61a5c015c2', 'db': 'http://mlrun-api:8080'}
+> 2024-03-27 15:34:45,675 [info] Job is running in the background, pod: process-data-process-data-89bhh
+> 2024-03-27 15:34:49,272 [info] Running with an existing cluster: {'cluster_id': '0327-134616-43m7kfxk'}
+> 2024-03-27 15:34:49,492 [info] Starting to poll: 493449112310004
+> 2024-03-27 15:34:49,539 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.PENDING
+> 2024-03-27 15:34:50,947 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.PENDING
+> 2024-03-27 15:34:53,063 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.RUNNING
+> 2024-03-27 15:34:56,737 [info] Workflow intermediate status: mlrun_task__15_34_48_703046: RunLifeCycleState.RUNNING
+> 2024-03-27 15:35:00,947 [info] Artifacts found. Run name: mlrun_task__15_34_48_703046
+> 2024-03-27 15:35:01,881 [info] Job finished: https://dbc-94c947ab-feb9.cloud.databricks.com/?o=4658245941722457#job/499259196347814/run/493449112310004
+> 2024-03-27 15:35:01,881 [info] Logs:
++-------------------+------------------+-------+-------+
+|favorite_music_type|           avg_age|min_age|max_age|
++-------------------+------------------+-------+-------+
+|               Rock|            30.125|     27|     34|
+|          Classical|47.666666666666664|     38|     75|
+|                Pop|              24.0|     18|     38|
++-------------------+------------------+-------+-------+
+
+2024-03-27 15:34:54,980 - mlrun_logger - INFO - successfully wrote artifact details to the artifact JSON file in DBFS - music_data : /dbfs/demos/mlrun_databricks_demo/music_output_new.parquet
+> 2024-03-27 15:35:02,182 [info] To track results use the CLI: {'info_cmd': 'mlrun get run a9c770f8377046bda3061e61a5c015c2 -p mlflow-tracking-example-guy', 'logs_cmd': 'mlrun logs a9c770f8377046bda3061e61a5c015c2 -p mlflow-tracking-example-guy'}
+> 2024-03-27 15:35:02,182 [info] Or click for UI: {'ui_url': 'https://dashboard.default-tenant.app.llm-dev.iguazio-cd1.com/mlprojects/mlflow-tracking-example-guy/jobs/monitor/a9c770f8377046bda3061e61a5c015c2/overview'}
+> 2024-03-27 15:35:02,182 [info] Run execution finished: {'status': 'completed', 'name': 'process-data-process-data'}
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
mlflow-tracking-example-guy0Mar 27 15:34:48completedprocess-data-process-data
v3io_user=zeevr
kind=databricks
owner=zeevr
mlrun/client_version=1.6.1
mlrun/client_python_version=3.9.16
host=process-data-process-data-89bhh
task_parameters={'timeout_minutes': 15, 'spark_app_code': 'IAoKaW1wb3J0IG9zCmltcG9ydCBsb2dnaW5nCm1scnVuX2xvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCdtbHJ1bl9sb2dnZXInKQptbHJ1bl9sb2dnZXIuc2V0TGV2ZWwobG9nZ2luZy5ERUJVRykKCm1scnVuX2NvbnNvbGVfaGFuZGxlciA9IGxvZ2dpbmcuU3RyZWFtSGFuZGxlcigpCm1scnVuX2NvbnNvbGVfaGFuZGxlci5zZXRMZXZlbChsb2dnaW5nLkRFQlVHKQptbHJ1bl9mb3JtYXR0ZXIgPSBsb2dnaW5nLkZvcm1hdHRlcignJShhc2N0aW1lKXMgLSAlKG5hbWUpcyAtICUobGV2ZWxuYW1lKXMgLSAlKG1lc3NhZ2UpcycpCm1scnVuX2NvbnNvbGVfaGFuZGxlci5zZXRGb3JtYXR0ZXIobWxydW5fZm9ybWF0dGVyKQptbHJ1bl9sb2dnZXIuYWRkSGFuZGxlcihtbHJ1bl9jb25zb2xlX2hhbmRsZXIpCgptbHJ1bl9kZWZhdWx0X2FydGlmYWN0X3RlbXBsYXRlID0gJ21scnVuX3JldHVybl92YWx1ZV8nCm1scnVuX2FydGlmYWN0X2luZGV4ID0gMAoKCmRlZiBtbHJ1bl9sb2dfYXJ0aWZhY3QobmFtZT0nJywgcGF0aD0nJyk6CiAgICBnbG9iYWwgbWxydW5fYXJ0aWZhY3RfaW5kZXgKICAgIG1scnVuX2FydGlmYWN0X2luZGV4Kz0xICAjICBieSBob3cgbWFueSBhcnRpZmFjdHMgd2UgdHJpZWQgdG8gbG9nLCBub3QgaG93IG1hbnkgc3VjY2VlZC4KICAgIGlmIG5hbWUgaXMgTm9uZSBvciBuYW1lID09ICcnOgogICAgICAgIG5hbWUgPSBmJ3ttbHJ1bl9kZWZhdWx0X2FydGlmYWN0X3RlbXBsYXRlfXttbHJ1bl9hcnRpZmFjdF9pbmRleH0nCiAgICBpZiBub3QgcGF0aDoKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZidwYXRoIHJlcXVpcmVkIGZvciBsb2dnaW5nIGFuIG1scnVuIGFydGlmYWN0IC0ge25hbWV9IDoge3BhdGh9JykKICAgICAgICByZXR1cm4KICAgIGlmIG5vdCBpc2luc3RhbmNlKG5hbWUsIHN0cikgb3Igbm90IGlzaW5zdGFuY2UocGF0aCwgc3RyKToKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZiduYW1lIGFuZCBwYXRoIG11c3QgYmUgaW4gc3RyaW5nIHR5cGUgZm9yIGxvZ2dpbmcgYW4gbWxydW4gYXJ0aWZhY3QgLSB7bmFtZX0gOiB7cGF0aH0nKQogICAgICAgIHJldHVybgogICAgaWYgbm90IHBhdGguc3RhcnRzd2l0aCgnL2RiZnMnKSBhbmQgbm90IHBhdGguc3RhcnRzd2l0aCgnZGJmczovJyk6CiAgICAgICAgbWxydW5fbG9nZ2VyLmVycm9yKGYncGF0aCBmb3IgYW4gbWxydW4gYXJ0aWZhY3QgbXVzdCBzdGFydCB3aXRoIC9kYmZzIG9yIGRiZnM6LyAtIHtuYW1lfSA6IHtwYXRofScpCiAgICAgICAgcmV0dXJuCiAgICBtbHJ1bl9hcnRpZmFjdHNfcGF0aCA9ICcvZGJmcy9tbHJ1bl9kYXRhYnJpY2tzX3J1bnRpbWUvYXJ0aWZhY3RzX2RpY3Rpb25hcmllcy9tbHJ1bl9hcnRpZmFjdF9hOWM3NzBmODM3NzA0NmJkYTMwNjFlNjFhNWMwMTVjMi5qc29uJwogICAgdHJ5OgogICAgICAgIG5ld19kYXRhID0ge25hbWU6cGF0aH0KICAgICAgICBpZiBvcy5wYXRoLmV4aXN0cyhtbHJ1bl9hcnRpZmFjdHNfcGF0aCk6CiAgICAgICAgICAgIHdpdGggb3BlbihtbHJ1bl9hcnRpZmFjdHNfcGF0aCwgJ3IrJykgYXMganNvbl9maWxlOgogICAgICAgICAgICAgICAgZXhpc3RpbmdfZGF0YSA9IGpzb24ubG9hZChqc29uX2ZpbGUpCiAgICAgICAgICAgICAgICBleGlzdGluZ19kYXRhLnVwZGF0ZShuZXdfZGF0YSkKICAgICAgICAgICAgICAgIGpzb25fZmlsZS5zZWVrKDApCiAgICAgICAgICAgICAgICBqc29uLmR1bXAoZXhpc3RpbmdfZGF0YSwganNvbl9maWxlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHBhcmVudF9kaXIgPSBvcy5wYXRoLmRpcm5hbWUobWxydW5fYXJ0aWZhY3RzX3BhdGgpCiAgICAgICAgICAgIGlmIHBhcmVudF9kaXIgIT0gJy9kYmZzJzoKICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKHBhcmVudF9kaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihtbHJ1bl9hcnRpZmFjdHNfcGF0aCwgJ3cnKSBhcyBqc29uX2ZpbGU6CiAgICAgICAgICAgICAgICBqc29uLmR1bXAobmV3X2RhdGEsIGpzb25fZmlsZSkKICAgICAgICBzdWNjZXNzX2xvZyA9IGYnc3VjY2Vzc2Z1bGx5IHdyb3RlIGFydGlmYWN0IGRldGFpbHMgdG8gdGhlIGFydGlmYWN0IEpTT04gZmlsZSBpbiBEQkZTIC0ge25hbWV9IDoge3BhdGh9JwogICAgICAgIG1scnVuX2xvZ2dlci5pbmZvKHN1Y2Nlc3NfbG9nKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyB1bmtub3duX2V4Y2VwdGlvbjoKICAgICAgICBtbHJ1bl9sb2dnZXIuZXJyb3IoZidsb2cgbWxydW4gYXJ0aWZhY3QgZmFpbGVkIC0ge25hbWV9IDoge3BhdGh9LiBlcnJvcjoge3Vua25vd25fZXhjZXB0aW9ufScpCgoKCgppbXBvcnQgYXJncGFyc2UKaW1wb3J0IGpzb24KcGFyc2VyID0gYXJncGFyc2UuQXJndW1lbnRQYXJzZXIoKQpwYXJzZXIuYWRkX2FyZ3VtZW50KCdoYW5kbGVyX2FyZ3VtZW50cycpCmhhbmRsZXJfYXJndW1lbnRzID0gcGFyc2VyLnBhcnNlX2FyZ3MoKS5oYW5kbGVyX2FyZ3VtZW50cwpoYW5kbGVyX2FyZ3VtZW50cyA9IGpzb24ubG9hZHMoaGFuZGxlcl9hcmd1bWVudHMpCgoKZnJvbSBweXNwYXJrLnNxbCBpbXBvcnQgU3BhcmtTZXNzaW9uCmZyb20gcHlzcGFyay5zcWwuZnVuY3Rpb25zIGltcG9ydCBhdmcsIG1pbiwgbWF4CmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IGpzb24KaW1wb3J0IGZzc3BlYwoKZGVmIHByb2Nlc3NfZGF0YShkYXRhX3BhdGg6IHN0ciwgZGF0YV9vdXRwdXRfcGF0aDogc3RyKToKICAgIHNwYXJrID0gU3BhcmtTZXNzaW9uLmJ1aWxkZXIuYXBwTmFtZSgnTXVzaWNEZW1vJykuZ2V0T3JDcmVhdGUoKQogICAgc3BhcmtfZGYgPSBzcGFyay5yZWFkLnBhcnF1ZXQoZGF0YV9wYXRoLCBoZWFkZXI9VHJ1ZSkKICAgIHNwYXJrX2RmID0gc3BhcmtfZGYuZHJvcCgnbmFtZScsICdpZCcpCiAgICBtdXNpY19zdGF0cyA9IHNwYXJrX2RmLmdyb3VwQnkoJ2Zhdm9yaXRlX211c2ljX3R5cGUnKS5hZ2coYXZnKCdhZ2UnKS5hbGlhcygnYXZnX2FnZScpLCBtaW4oJ2FnZScpLmFsaWFzKCdtaW5fYWdlJyksIG1heCgnYWdlJykuYWxpYXMoJ21heF9hZ2UnKSkKICAgIG11c2ljX3N0YXRzLnNob3coKQogICAgcGFuZGFzX2RmID0gc3BhcmtfZGYudG9QYW5kYXMoKQogICAgcGFuZGFzX2RmLnRvX3BhcnF1ZXQoZGF0YV9vdXRwdXRfcGF0aCkKICAgIHJldHVybiB7J211c2ljX2RhdGEnOiBkYXRhX291dHB1dF9wYXRofQpyZXN1bHQgPSBwcm9jZXNzX2RhdGEoKipoYW5kbGVyX2FyZ3VtZW50cykKCgppZiByZXN1bHQ6CiAgICBpZiBpc2luc3RhbmNlKHJlc3VsdCwgZGljdCk6CiAgICAgICAgZm9yIGtleSwgcGF0aCBpbiByZXN1bHQuaXRlbXMoKToKICAgICAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KG5hbWU9a2V5LCBwYXRoPXBhdGgpCiAgICBlbGlmIGlzaW5zdGFuY2UocmVzdWx0LCAobGlzdCwgdHVwbGUsIHNldCkpOgogICAgICAgIGZvciBhcnRpZmFjdF9wYXRoIGluIHJlc3VsdDoKICAgICAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KHBhdGg9YXJ0aWZhY3RfcGF0aCkKICAgIGVsaWYgaXNpbnN0YW5jZShyZXN1bHQsIHN0cik6CiAgICAgICAgbWxydW5fbG9nX2FydGlmYWN0KHBhdGg9cmVzdWx0KQogICAgZWxzZToKICAgICAgICBtbHJ1bl9sb2dnZXIud2FybmluZyhmJ2NhbiBub3QgbG9nIGFydGlmYWN0cyB3aXRoIHRoZSByZXN1bHQgb2YgaGFuZGxlciBmdW5jdGlvbiAtIHJlc3VsdCBpbiB1bnN1cHBvcnRlZCB0eXBlLiB7dHlwZShyZXN1bHQpfScpCg==', 'original_handler': 'process_data', 'artifact_json_path': '/mlrun_databricks_runtime/artifacts_dictionaries/mlrun_artifact_a9c770f8377046bda3061e61a5c015c2.json'}
data_path=dbfs:///demos/mlrun_databricks_demo/1711553684480_33/music.parquet
data_output_path=/dbfs/demos/mlrun_databricks_demo/music_output_new.parquet
music_data
databricks_run_metadata
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2024-03-27 15:35:07,910 [info] Run execution finished: {'status': 'completed', 'name': 'process-data-process-data'}
+
+
+
+
+
+
+

Create an MLflow Xgboost function#

+

The following code demonstrates how to create a simple Xgboost model using MLflow and log the results. +MLflow will log the model, parameters, metrics, and artifacts, and MLRun will track the run and collect the data.

+
+
+
%%writefile training.py
+
+import mlflow
+import mlflow.xgboost
+import xgboost as xgb
+from mlflow import log_metric
+from sklearn import datasets
+from sklearn.metrics import accuracy_score, log_loss
+from sklearn.model_selection import train_test_split
+import pandas as pd
+
+def example_xgb_run(df: str):
+    df = pd.read_parquet(df)
+    
+    df = df.replace(["f", "m"], [0, 1])
+    df = df.replace(["Pop", "Rock", "Classical"], [0, 1, 2])
+    
+    # Prepare, train, and test data
+    y = df.pop('favorite_music_type')
+    X = df
+
+    X_train, X_test, y_train, y_test = train_test_split(
+        X, y, test_size=0.2, random_state=42
+    )
+
+    # Enable auto logging
+    mlflow.xgboost.autolog()
+
+    dtrain = xgb.DMatrix(X_train, label=y_train)
+    dtest = xgb.DMatrix(X_test, label=y_test)
+
+    with mlflow.start_run():
+        # Train model
+        params = {
+            "objective": "multi:softprob",
+            "num_class": 3,
+            "learning_rate": 0.3,
+            "eval_metric": "mlogloss",
+            "colsample_bytree": 1.0,
+            "subsample": 1.0,
+            "seed": 42,
+        }
+        model = xgb.train(params, dtrain, evals=[(dtrain, "train")])
+        
+        # Evaluate model
+        y_proba = model.predict(dtest)
+        y_pred = y_proba.argmax(axis=1)
+        loss = log_loss(y_test, y_proba)
+        acc = accuracy_score(y_test, y_pred)
+        
+        # Log metrics by hand
+        mlflow.log_metrics({"log_loss": loss, "accuracy": acc})
+
+
+
+
+
Overwriting training.py
+
+
+
+
+
+
+

Log the data from MLflow in MLRun#

+
+

Change the MLRun configuration to use the tracker#

+
+
+
import mlrun
+
+mlrun.mlconf.external_platform_tracking.enabled = True
+
+
+
+
+

These are the three options to run tracking:

+
    +
  • Set: mlrun.mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime to True. This determines the run id and is the safest method

  • +
  • Set the experiment name at: mlflow.environment_variables.MLFLOW_EXPERIMENT_NAME.set. This determines the experiment mlrun will track and find the run added to it.

  • +
  • Just run it, mlrun will look across all experiments and search for added run, this is not recomended.

  • +
+
+
+

Create the mlrun function#

+
+
+
# Use the first run option from above
+mlrun.mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime = True
+
+# Create a MLRun function using the example train file (all the functions must be located in it):
+training_func = project.set_function(
+    func="training.py",
+    name="example-xgb-run",
+    kind="job",
+    image="mlrun/mlrun",
+)
+
+
+
+
+
+
+

Run the function#

+

Run the function using MLRun. This will log the data from MLflow in MLRun. +After running the function, you can look at the UI and see that all metrics and parameters are logged in MLRun.

+
+
+
import mlrun.feature_store as fstore
+
+feature_set = fstore.get_feature_set("music_fset", "mlflow-tracking-example")
+
+
+
+
+
+
+
df = feature_set.to_dataframe()
+df = df.drop(['id'], axis=1)
+
+
+
+
+
+
+
# df = project.list_().to_objects()[0].to_dataitem().as_df()
+df_path = "./music.parquet"
+df.to_parquet(df_path)
+
+
+
+
+
+
+
# Run the example code using mlrun
+train_run = training_func.run(
+    local=True,
+    handler="example_xgb_run",
+    inputs={"df": df_path},
+)
+
+
+
+
+
> 2024-03-27 15:37:22,829 [info] Storing function: {'name': 'example-xgb-run-example-xgb-run', 'uid': '6ff324dd21d64b6290d45a001957dda2', 'db': 'http://mlrun-api:8080'}
+> 2024-03-27 15:37:22,912 [warning] `mlconf.external_platform_tracking.mlflow.match_experiment_to_runtime` is set to True but the MLFlow experiment name environment variable ('MLFLOW_EXPERIMENT_NAME') is set for using the name: 'example-xgb-run-example-xgb-run'. This name will be overriden with MLRun's runtime name as set in the MLRun configuration: 'example-xgb-run-example-xgb-run'.
+[0]	train-mlogloss:0.82467
+[1]	train-mlogloss:0.64706
+[2]	train-mlogloss:0.52480
+[3]	train-mlogloss:0.43768
+[4]	train-mlogloss:0.37410
+[5]	train-mlogloss:0.32686
+[6]	train-mlogloss:0.29057
+[7]	train-mlogloss:0.26192
+[8]	train-mlogloss:0.23885
+[9]	train-mlogloss:0.22004
+
+
+
2024/03/27 15:37:23 WARNING mlflow.utils.autologging_utils: MLflow autologging encountered a warning: "/User/.pythonlibs/mlrun-base/lib/python3.9/site-packages/mlflow/types/utils.py:393: UserWarning: Hint: Inferred schema contains integer column(s). Integer columns in Python cannot represent missing values. If your input data contains missing values at inference time, it will be encoded as floats and will cause a schema enforcement error. The best way to avoid this problem is to infer the model schema based on a realistic data sample (training dataset) that includes missing values. Alternatively, you can declare integer columns as doubles (float64) whenever these columns may have missing values. See `Handling Integers With Missing Values <https://www.mlflow.org/docs/latest/models.html#handling-integers-with-missing-values>`_ for more details."
+2024/03/27 15:37:23 WARNING mlflow.utils.autologging_utils: MLflow autologging encountered a warning: "/User/.pythonlibs/mlrun-base/lib/python3.9/site-packages/xgboost/core.py:160: UserWarning: [15:37:23] WARNING: /workspace/src/c_api/c_api.cc:1240: Saving into deprecated binary model format, please consider using `json` or `ubj`. Model format will default to JSON in XGBoost 2.2 if not specified."
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
mlflow-tracking-example-guy0Mar 27 15:37:22completedexample-xgb-run-example-xgb-run
v3io_user=zeevr
kind=local
owner=zeevr
host=jupyter-zeevr-9f4ffb7bb-8c4mf
mlflow-user=iguazio
mlflow-run-name=stately-cow-437
mlflow-run-id=f66d6149d54c4958a2485c941d86a538
mlflow-experiment-id=608717337209571124
df
colsample_bytree=1.0
custom_metric=None
early_stopping_rounds=None
eval_metric=mlogloss
learning_rate=0.3
maximize=None
num_boost_round=10
num_class=3
objective=multi:softprob
seed=42
subsample=1.0
verbose_eval=True
accuracy=0.7142857142857143
log_loss=0.9622776094122579
train-mlogloss=0.2200447738170624
feature_importance_weight_json
feature_importance_weight_png
model
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2024-03-27 15:37:31,415 [info] Run execution finished: {'status': 'completed', 'name': 'example-xgb-run-example-xgb-run'}
+
+
+
+
+
+
+

Examine the results#

+

You can examine the results using the UI or by looking at the outputs of the run. +The outputs include the model, the metrics, and the artifacts, and are completely independent of MLflow.

+
+
+
train_run.outputs
+
+
+
+
+
{'accuracy': 0.7142857142857143,
+ 'log_loss': 0.9622776094122579,
+ 'train-mlogloss': 0.2200447738170624,
+ 'feature_importance_weight_json': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_feature_importance_weight_json@6ff324dd21d64b6290d45a001957dda2',
+ 'feature_importance_weight_png': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_feature_importance_weight_png@6ff324dd21d64b6290d45a001957dda2',
+ 'model': 'store://artifacts/mlflow-tracking-example-guy/example-xgb-run-example-xgb-run_model@6ff324dd21d64b6290d45a001957dda2'}
+
+
+
+
+
+
+
train_run.status.results
+
+
+
+
+
{'accuracy': 0.7142857142857143,
+ 'log_loss': 0.9622776094122579,
+ 'train-mlogloss': 0.2200447738170624}
+
+
+
+
+
+
+
train_run.artifact("feature_importance_weight_png").show()
+
+
+
+
+_images/3edebfb8ca78ff860681bc18084495dd1fefd44d69ead3b64c7b3e0b0170adbb.png +
+
+
+
+

You can also examine the results using the UI#

+

Look at collected artifacts:

+

image.png

+

And at results:

+

image.png

+
+
+
+

Use the function for model serving#

+
+

Create the server and serving function#

+

Create a serving function that uses the model from the previous run and serves it using MLRun. +We will create a mock server to test the model in a local environment.

+
+
+
serving_func = project.set_function(
+    func="function.yaml",
+    name="example-xgb-server",
+)
+
+
+
+
+
+
+
# Add the model
+serving_func.add_model(
+    "mlflow_xgb_model",
+    class_name="MLFlowModelServer",
+    model_path=train_run.outputs["model"],
+)
+
+
+
+
+
<mlrun.serving.states.TaskStep at 0x7f77c3e4c9a0>
+
+
+
+
+
+
+
# Create a mock server
+server = serving_func.to_mock_server()
+
+
+
+
+
> 2024-03-27 15:37:31,627 [info] model mlflow_xgb_model was loaded
+> 2024-03-27 15:37:31,628 [info] Loaded ['mlflow_xgb_model']
+
+
+
+
+
+
+

Test the model#

+
+
+
# An example taken randomly  
+result = server.test("/v2/models/mlflow_xgb_model/predict", {"inputs":[{"age": 20, "gender": 0}]})
+
+
+
+
+
+
+
# Look at the result, it shows the probability of the given example to be each of the 
+# irises featured in the dataset
+result
+
+
+
+
+
{'id': '43a61d06f2694fa695bdd6561b487131',
+ 'model_name': 'mlflow_xgb_model',
+ 'outputs': [[0.9242361187934875, 0.0418272465467453, 0.033936627209186554]]}
+
+
+
+
+

We predicted that a 20 year old female would like pop!

+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/1.1.0/static/function.html b/functions/master/mlflow_utils/1.1.0/static/function.html new file mode 100644 index 00000000..d0b2dd51 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/function.html @@ -0,0 +1,67 @@ + + + + + + + + + + + Source + + + + +
+        
+verbose: false
+spec:
+  command: ''
+  source: ''
+  default_class: MLFlowModelServer
+  function_kind: serving_v2
+  build:
+    functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhIG1vZGVsIHRoYXQgd2FzIGxvZ2dlZCBieSB0aGUgTUxGbG93IHRyYWNrZXIgbW9kZWwKICAgICAgICAiIiIKICAgICAgICAjIFVuemlwIHRoZSBtb2RlbCBkaXIgYW5kIHRoZW4gdXNlIG1sZmxvdydzIGxvYWQgZnVuY3Rpb24KICAgICAgICBtb2RlbF9maWxlLCBfID0gc2VsZi5nZXRfbW9kZWwoIi56aXAiKQogICAgICAgIG1vZGVsX3BhdGhfdW56aXAgPSBtb2RlbF9maWxlLnJlcGxhY2UoIi56aXAiLCAiIikKCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUobW9kZWxfZmlsZSwgInIiKSBhcyB6aXBfcmVmOgogICAgICAgICAgICB6aXBfcmVmLmV4dHJhY3RhbGwobW9kZWxfcGF0aF91bnppcCkKCiAgICAgICAgc2VsZi5tb2RlbCA9IG1sZmxvdy5weWZ1bmMubG9hZF9tb2RlbChtb2RlbF9wYXRoX3VuemlwKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIHJlcXVlc3Q6IERpY3Rbc3RyLCBBbnldKSAtPiBsaXN0OgogICAgICAgICIiIgogICAgICAgIEluZmVyIHRoZSBpbnB1dHMgdGhyb3VnaCB0aGUgbW9kZWwuIFRoZSBpbmZlcnJlZCBkYXRhIHdpbGwKICAgICAgICBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleSBvZiB0aGUgcmVxdWVzdC4KCiAgICAgICAgOnBhcmFtIHJlcXVlc3Q6IFRoZSByZXF1ZXN0IHRvIHRoZSBtb2RlbCB1c2luZyB4Z2Jvb3N0J3MgcHJlZGljdC4KICAgICAgICAgICAgICAgIFRoZSBpbnB1dCB0byB0aGUgbW9kZWwgd2lsbCBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleS4KCiAgICAgICAgOnJldHVybjogVGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBvbiB0aGUgZ2l2ZW4gaW5wdXQuCiAgICAgICAgIiIiCgogICAgICAgICMgR2V0IHRoZSBpbnB1dHMgYW5kIHNldCB0byBhY2NlcHRlZCB0eXBlOgogICAgICAgIGlucHV0cyA9IHBkLkRhdGFGcmFtZShyZXF1ZXN0WyJpbnB1dHMiXSkKCiAgICAgICAgIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCdzIHByZWRpY3QgZnVuY3Rpb246CiAgICAgICAgcHJlZGljdGlvbnMgPSBzZWxmLm1vZGVsLnByZWRpY3QoaW5wdXRzKQoKICAgICAgICAjIFJldHVybiBhcyBsaXN0OgogICAgICAgIHJldHVybiBwcmVkaWN0aW9ucy50b2xpc3QoKQoKZnJvbSBtbHJ1bi5ydW50aW1lcyBpbXBvcnQgbnVjbGlvX2luaXRfaG9vawpkZWYgaW5pdF9jb250ZXh0KGNvbnRleHQpOgogICAgbnVjbGlvX2luaXRfaG9vayhjb250ZXh0LCBnbG9iYWxzKCksICdzZXJ2aW5nX3YyJykKCmRlZiBoYW5kbGVyKGNvbnRleHQsIGV2ZW50KToKICAgIHJldHVybiBjb250ZXh0Lm1scnVuX2hhbmRsZXIoY29udGV4dCwgZXZlbnQpCg==
+    requirements:
+    - mlflow==2.12.2
+    - lightgbm
+    - xgboost
+    code_origin: ''
+    origin_filename: ''
+  image: mlrun/mlrun
+  base_image_pull: false
+  default_handler: ''
+  max_replicas: 4
+  disable_auto_mount: false
+  min_replicas: 1
+  description: Mlflow model server, and additional utils.
+  function_handler: mlflow-utils-nuclio:handler
+  env:
+  - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK
+    value: enabled
+metadata:
+  categories:
+  - model-serving
+  - utils
+  name: mlflow-utils
+  tag: ''
+kind: serving
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/1.1.0/static/item.html b/functions/master/mlflow_utils/1.1.0/static/item.html new file mode 100644 index 00000000..d193c1fa --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/item.html @@ -0,0 +1,65 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+- model-serving
+- utils
+description: Mlflow model server, and additional utils.
+doc: ''
+example: mlflow_utils.ipynb
+generationDate: 2024-05-23:12-00
+hidden: false
+icon: ''
+labels:
+  author: zeevr
+maintainers: []
+marketplaceType: ''
+mlrunVersion: 1.8.0
+name: mlflow_utils
+platformVersion: ''
+spec:
+  customFields:
+    default_class: MLFlowModelServer
+  filename: mlflow_utils.py
+  handler: handler
+  image: mlrun/mlrun
+  kind: serving
+  requirements:
+  - mlflow==2.12.2
+  - lightgbm
+  - xgboost
+url: ''
+version: 1.1.0
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/1.1.0/static/mlflow_utils.html b/functions/master/mlflow_utils/1.1.0/static/mlflow_utils.html new file mode 100644 index 00000000..d3e1f984 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/mlflow_utils.html @@ -0,0 +1,226 @@ + + + + + + + +mlflow_utils.mlflow_utils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ +
+
+
+
+
+ +
+

Source code for mlflow_utils.mlflow_utils

+import zipfile
+from typing import Any, Dict
+import mlflow
+from mlrun.serving.v2_serving import V2ModelServer
+import pandas as pd
+
+
+
+[docs] +class MLFlowModelServer(V2ModelServer): + """ + MLFlow tracker Model serving class, inheriting the V2ModelServer class for being initialized automatically by the model + server and be able to run locally as part of a nuclio serverless function, or as part of a real-time pipeline. + """ + +
+[docs] + def load(self): + """ + loads a model that was logged by the MLFlow tracker model + """ + # Unzip the model dir and then use mlflow's load function + model_file, _ = self.get_model(".zip") + model_path_unzip = model_file.replace(".zip", "") + + with zipfile.ZipFile(model_file, "r") as zip_ref: + zip_ref.extractall(model_path_unzip) + + self.model = mlflow.pyfunc.load_model(model_path_unzip)
+ + +
+[docs] + def predict(self, request: Dict[str, Any]) -> list: + """ + Infer the inputs through the model. The inferred data will + be read from the "inputs" key of the request. + + :param request: The request to the model using xgboost's predict. + The input to the model will be read from the "inputs" key. + + :return: The model's prediction on the given input. + """ + + # Get the inputs and set to accepted type: + inputs = pd.DataFrame(request["inputs"]) + + # Predict using the model's predict function: + predictions = self.model.predict(inputs) + + # Return as list: + return predictions.tolist()
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/1.1.0/static/source.html b/functions/master/mlflow_utils/1.1.0/static/source.html new file mode 100644 index 00000000..fe11b2c1 --- /dev/null +++ b/functions/master/mlflow_utils/1.1.0/static/source.html @@ -0,0 +1,80 @@ + + + + + + + + + + + Source + + + + +
+        
+import zipfile
+from typing import Any, Dict
+import mlflow
+from mlrun.serving.v2_serving import V2ModelServer
+import pandas as pd
+
+
+class MLFlowModelServer(V2ModelServer):
+    """
+    MLFlow tracker Model serving class, inheriting the V2ModelServer class for being initialized automatically by the model
+    server and be able to run locally as part of a nuclio serverless function, or as part of a real-time pipeline.
+    """
+
+    def load(self):
+        """
+        loads a model that was logged by the MLFlow tracker model
+        """
+        # Unzip the model dir and then use mlflow's load function
+        model_file, _ = self.get_model(".zip")
+        model_path_unzip = model_file.replace(".zip", "")
+
+        with zipfile.ZipFile(model_file, "r") as zip_ref:
+            zip_ref.extractall(model_path_unzip)
+
+        self.model = mlflow.pyfunc.load_model(model_path_unzip)
+
+    def predict(self, request: Dict[str, Any]) -> list:
+        """
+        Infer the inputs through the model. The inferred data will
+        be read from the "inputs" key of the request.
+
+        :param request: The request to the model using xgboost's predict.
+                The input to the model will be read from the "inputs" key.
+
+        :return: The model's prediction on the given input.
+        """
+
+        # Get the inputs and set to accepted type:
+        inputs = pd.DataFrame(request["inputs"])
+
+        # Predict using the model's predict function:
+        predictions = self.model.predict(inputs)
+
+        # Return as list:
+        return predictions.tolist()
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/mlflow_utils/latest/src/function.yaml b/functions/master/mlflow_utils/latest/src/function.yaml index d2e2bffe..623f054f 100644 --- a/functions/master/mlflow_utils/latest/src/function.yaml +++ b/functions/master/mlflow_utils/latest/src/function.yaml @@ -1,31 +1,32 @@ -metadata: - name: mlflow-utils - categories: - - genai - - model-serving - - machine-learning - tag: '' +verbose: false spec: - default_handler: '' - image: mlrun/mlrun command: '' - base_image_pull: false + source: '' default_class: MLFlowModelServer - function_handler: mlflow-utils:handler - disable_auto_mount: false + function_kind: serving_v2 build: - origin_filename: '' - code_origin: '' + functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhIG1vZGVsIHRoYXQgd2FzIGxvZ2dlZCBieSB0aGUgTUxGbG93IHRyYWNrZXIgbW9kZWwKICAgICAgICAiIiIKICAgICAgICAjIFVuemlwIHRoZSBtb2RlbCBkaXIgYW5kIHRoZW4gdXNlIG1sZmxvdydzIGxvYWQgZnVuY3Rpb24KICAgICAgICBtb2RlbF9maWxlLCBfID0gc2VsZi5nZXRfbW9kZWwoIi56aXAiKQogICAgICAgIG1vZGVsX3BhdGhfdW56aXAgPSBtb2RlbF9maWxlLnJlcGxhY2UoIi56aXAiLCAiIikKCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUobW9kZWxfZmlsZSwgInIiKSBhcyB6aXBfcmVmOgogICAgICAgICAgICB6aXBfcmVmLmV4dHJhY3RhbGwobW9kZWxfcGF0aF91bnppcCkKCiAgICAgICAgc2VsZi5tb2RlbCA9IG1sZmxvdy5weWZ1bmMubG9hZF9tb2RlbChtb2RlbF9wYXRoX3VuemlwKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIHJlcXVlc3Q6IERpY3Rbc3RyLCBBbnldKSAtPiBsaXN0OgogICAgICAgICIiIgogICAgICAgIEluZmVyIHRoZSBpbnB1dHMgdGhyb3VnaCB0aGUgbW9kZWwuIFRoZSBpbmZlcnJlZCBkYXRhIHdpbGwKICAgICAgICBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleSBvZiB0aGUgcmVxdWVzdC4KCiAgICAgICAgOnBhcmFtIHJlcXVlc3Q6IFRoZSByZXF1ZXN0IHRvIHRoZSBtb2RlbCB1c2luZyB4Z2Jvb3N0J3MgcHJlZGljdC4KICAgICAgICAgICAgICAgIFRoZSBpbnB1dCB0byB0aGUgbW9kZWwgd2lsbCBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleS4KCiAgICAgICAgOnJldHVybjogVGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBvbiB0aGUgZ2l2ZW4gaW5wdXQuCiAgICAgICAgIiIiCgogICAgICAgICMgR2V0IHRoZSBpbnB1dHMgYW5kIHNldCB0byBhY2NlcHRlZCB0eXBlOgogICAgICAgIGlucHV0cyA9IHBkLkRhdGFGcmFtZShyZXF1ZXN0WyJpbnB1dHMiXSkKCiAgICAgICAgIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCdzIHByZWRpY3QgZnVuY3Rpb246CiAgICAgICAgcHJlZGljdGlvbnMgPSBzZWxmLm1vZGVsLnByZWRpY3QoaW5wdXRzKQoKICAgICAgICAjIFJldHVybiBhcyBsaXN0OgogICAgICAgIHJldHVybiBwcmVkaWN0aW9ucy50b2xpc3QoKQoKZnJvbSBtbHJ1bi5ydW50aW1lcyBpbXBvcnQgbnVjbGlvX2luaXRfaG9vawpkZWYgaW5pdF9jb250ZXh0KGNvbnRleHQpOgogICAgbnVjbGlvX2luaXRfaG9vayhjb250ZXh0LCBnbG9iYWxzKCksICdzZXJ2aW5nX3YyJykKCmRlZiBoYW5kbGVyKGNvbnRleHQsIGV2ZW50KToKICAgIHJldHVybiBjb250ZXh0Lm1scnVuX2hhbmRsZXIoY29udGV4dCwgZXZlbnQpCg== requirements: - mlflow==2.12.2 - functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhbiBtb2RlbCB0aGF0IHdhcyBsb2dnZWQgYnkgdGhlIE1MRmxvdyB0cmFja2VyIG1vZGVsCiAgICAgICAgIiIiCiAgICAgICAgIyBVbnppcCB0aGUgbW9kZWwgZGlyIGFuZCB0aGVuIHVzZSBtbGZsb3cncyBsb2FkIGZ1bmN0aW9uCiAgICAgICAgbW9kZWxfZmlsZSwgXyA9IHNlbGYuZ2V0X21vZGVsKCIuemlwIikKICAgICAgICBtb2RlbF9wYXRoX3VuemlwID0gbW9kZWxfZmlsZS5yZXBsYWNlKCIuemlwIiwgIiIpCgogICAgICAgIHdpdGggemlwZmlsZS5aaXBGaWxlKG1vZGVsX2ZpbGUsICJyIikgYXMgemlwX3JlZjoKICAgICAgICAgICAgemlwX3JlZi5leHRyYWN0YWxsKG1vZGVsX3BhdGhfdW56aXApCgogICAgICAgIHNlbGYubW9kZWwgPSBtbGZsb3cucHlmdW5jLmxvYWRfbW9kZWwobW9kZWxfcGF0aF91bnppcCkKCiAgICBkZWYgcHJlZGljdChzZWxmLCByZXF1ZXN0OiBEaWN0W3N0ciwgQW55XSkgLT4gbGlzdDoKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgaW5wdXRzIHRocm91Z2ggdGhlIG1vZGVsLiBUaGUgaW5mZXJyZWQgZGF0YSB3aWxsCiAgICAgICAgYmUgcmVhZCBmcm9tIHRoZSAiaW5wdXRzIiBrZXkgb2YgdGhlIHJlcXVlc3QuCgogICAgICAgIDpwYXJhbSByZXF1ZXN0OiBUaGUgcmVxdWVzdCB0byB0aGUgbW9kZWwgdXNpbmcgeGdib29zdCdzIHByZWRpY3QuCiAgICAgICAgICAgICAgICBUaGUgaW5wdXQgdG8gdGhlIG1vZGVsIHdpbGwgYmUgcmVhZCBmcm9tIHRoZSAiaW5wdXRzIiBrZXkuCgogICAgICAgIDpyZXR1cm46IFRoZSBtb2RlbCdzIHByZWRpY3Rpb24gb24gdGhlIGdpdmVuIGlucHV0LgogICAgICAgICIiIgoKICAgICAgICAjIEdldCB0aGUgaW5wdXRzIGFuZCBzZXQgdG8gYWNjZXB0ZWQgdHlwZToKICAgICAgICBpbnB1dHMgPSBwZC5EYXRhRnJhbWUocmVxdWVzdFsiaW5wdXRzIl0pCgogICAgICAgICMgUHJlZGljdCB1c2luZyB0aGUgbW9kZWwncyBwcmVkaWN0IGZ1bmN0aW9uOgogICAgICAgIHByZWRpY3Rpb25zID0gc2VsZi5tb2RlbC5wcmVkaWN0KGlucHV0cykKCiAgICAgICAgIyBSZXR1cm4gYXMgbGlzdDoKICAgICAgICByZXR1cm4gcHJlZGljdGlvbnMudG9saXN0KCkKCmZyb20gbWxydW4ucnVudGltZXMgaW1wb3J0IG51Y2xpb19pbml0X2hvb2sKZGVmIGluaXRfY29udGV4dChjb250ZXh0KToKICAgIG51Y2xpb19pbml0X2hvb2soY29udGV4dCwgZ2xvYmFscygpLCAnc2VydmluZ192MicpCgpkZWYgaGFuZGxlcihjb250ZXh0LCBldmVudCk6CiAgICByZXR1cm4gY29udGV4dC5tbHJ1bl9oYW5kbGVyKGNvbnRleHQsIGV2ZW50KQo= + - lightgbm + - xgboost + code_origin: '' + origin_filename: '' + image: mlrun/mlrun + base_image_pull: false + default_handler: '' + max_replicas: 4 + disable_auto_mount: false min_replicas: 1 description: Mlflow model server, and additional utils. - max_replicas: 4 - source: '' - function_kind: serving_v2 + function_handler: mlflow-utils-nuclio:handler env: - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK value: enabled -verbose: false +metadata: + categories: + - model-serving + - utils + name: mlflow-utils + tag: '' kind: serving diff --git a/functions/master/mlflow_utils/latest/src/item.yaml b/functions/master/mlflow_utils/latest/src/item.yaml index bda09c5b..27e61ab4 100644 --- a/functions/master/mlflow_utils/latest/src/item.yaml +++ b/functions/master/mlflow_utils/latest/src/item.yaml @@ -1,8 +1,7 @@ apiVersion: v1 categories: -- genai - model-serving -- machine-learning +- utils description: Mlflow model server, and additional utils. doc: '' example: mlflow_utils.ipynb @@ -13,7 +12,7 @@ labels: author: zeevr maintainers: [] marketplaceType: '' -mlrunVersion: 1.7.0-rc17 +mlrunVersion: 1.8.0 name: mlflow_utils platformVersion: '' spec: @@ -28,4 +27,4 @@ spec: - lightgbm - xgboost url: '' -version: 1.0.0 +version: 1.1.0 diff --git a/functions/master/mlflow_utils/latest/static/documentation.html b/functions/master/mlflow_utils/latest/static/documentation.html index 5cd55de2..ff93711e 100644 --- a/functions/master/mlflow_utils/latest/static/documentation.html +++ b/functions/master/mlflow_utils/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/mlflow_utils/latest/static/example.html b/functions/master/mlflow_utils/latest/static/example.html index 63974c6a..72f59a9b 100644 --- a/functions/master/mlflow_utils/latest/static/example.html +++ b/functions/master/mlflow_utils/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/mlflow_utils/latest/static/function.html b/functions/master/mlflow_utils/latest/static/function.html index cbc50484..d0b2dd51 100644 --- a/functions/master/mlflow_utils/latest/static/function.html +++ b/functions/master/mlflow_utils/latest/static/function.html @@ -28,36 +28,37 @@
         
-metadata:
-  name: mlflow-utils
-  categories:
-  - genai
-  - model-serving
-  - machine-learning
-  tag: ''
+verbose: false
 spec:
-  default_handler: ''
-  image: mlrun/mlrun
   command: ''
-  base_image_pull: false
+  source: ''
   default_class: MLFlowModelServer
-  function_handler: mlflow-utils:handler
-  disable_auto_mount: false
+  function_kind: serving_v2
   build:
-    origin_filename: ''
-    code_origin: ''
+    functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhIG1vZGVsIHRoYXQgd2FzIGxvZ2dlZCBieSB0aGUgTUxGbG93IHRyYWNrZXIgbW9kZWwKICAgICAgICAiIiIKICAgICAgICAjIFVuemlwIHRoZSBtb2RlbCBkaXIgYW5kIHRoZW4gdXNlIG1sZmxvdydzIGxvYWQgZnVuY3Rpb24KICAgICAgICBtb2RlbF9maWxlLCBfID0gc2VsZi5nZXRfbW9kZWwoIi56aXAiKQogICAgICAgIG1vZGVsX3BhdGhfdW56aXAgPSBtb2RlbF9maWxlLnJlcGxhY2UoIi56aXAiLCAiIikKCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUobW9kZWxfZmlsZSwgInIiKSBhcyB6aXBfcmVmOgogICAgICAgICAgICB6aXBfcmVmLmV4dHJhY3RhbGwobW9kZWxfcGF0aF91bnppcCkKCiAgICAgICAgc2VsZi5tb2RlbCA9IG1sZmxvdy5weWZ1bmMubG9hZF9tb2RlbChtb2RlbF9wYXRoX3VuemlwKQoKICAgIGRlZiBwcmVkaWN0KHNlbGYsIHJlcXVlc3Q6IERpY3Rbc3RyLCBBbnldKSAtPiBsaXN0OgogICAgICAgICIiIgogICAgICAgIEluZmVyIHRoZSBpbnB1dHMgdGhyb3VnaCB0aGUgbW9kZWwuIFRoZSBpbmZlcnJlZCBkYXRhIHdpbGwKICAgICAgICBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleSBvZiB0aGUgcmVxdWVzdC4KCiAgICAgICAgOnBhcmFtIHJlcXVlc3Q6IFRoZSByZXF1ZXN0IHRvIHRoZSBtb2RlbCB1c2luZyB4Z2Jvb3N0J3MgcHJlZGljdC4KICAgICAgICAgICAgICAgIFRoZSBpbnB1dCB0byB0aGUgbW9kZWwgd2lsbCBiZSByZWFkIGZyb20gdGhlICJpbnB1dHMiIGtleS4KCiAgICAgICAgOnJldHVybjogVGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBvbiB0aGUgZ2l2ZW4gaW5wdXQuCiAgICAgICAgIiIiCgogICAgICAgICMgR2V0IHRoZSBpbnB1dHMgYW5kIHNldCB0byBhY2NlcHRlZCB0eXBlOgogICAgICAgIGlucHV0cyA9IHBkLkRhdGFGcmFtZShyZXF1ZXN0WyJpbnB1dHMiXSkKCiAgICAgICAgIyBQcmVkaWN0IHVzaW5nIHRoZSBtb2RlbCdzIHByZWRpY3QgZnVuY3Rpb246CiAgICAgICAgcHJlZGljdGlvbnMgPSBzZWxmLm1vZGVsLnByZWRpY3QoaW5wdXRzKQoKICAgICAgICAjIFJldHVybiBhcyBsaXN0OgogICAgICAgIHJldHVybiBwcmVkaWN0aW9ucy50b2xpc3QoKQoKZnJvbSBtbHJ1bi5ydW50aW1lcyBpbXBvcnQgbnVjbGlvX2luaXRfaG9vawpkZWYgaW5pdF9jb250ZXh0KGNvbnRleHQpOgogICAgbnVjbGlvX2luaXRfaG9vayhjb250ZXh0LCBnbG9iYWxzKCksICdzZXJ2aW5nX3YyJykKCmRlZiBoYW5kbGVyKGNvbnRleHQsIGV2ZW50KToKICAgIHJldHVybiBjb250ZXh0Lm1scnVuX2hhbmRsZXIoY29udGV4dCwgZXZlbnQpCg==
     requirements:
     - mlflow==2.12.2
-    functionSourceCode: aW1wb3J0IHppcGZpbGUKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdAppbXBvcnQgbWxmbG93CmZyb20gbWxydW4uc2VydmluZy52Ml9zZXJ2aW5nIGltcG9ydCBWMk1vZGVsU2VydmVyCmltcG9ydCBwYW5kYXMgYXMgcGQKCgpjbGFzcyBNTEZsb3dNb2RlbFNlcnZlcihWMk1vZGVsU2VydmVyKToKICAgICIiIgogICAgTUxGbG93IHRyYWNrZXIgTW9kZWwgc2VydmluZyBjbGFzcywgaW5oZXJpdGluZyB0aGUgVjJNb2RlbFNlcnZlciBjbGFzcyBmb3IgYmVpbmcgaW5pdGlhbGl6ZWQgYXV0b21hdGljYWxseSBieSB0aGUgbW9kZWwKICAgIHNlcnZlciBhbmQgYmUgYWJsZSB0byBydW4gbG9jYWxseSBhcyBwYXJ0IG9mIGEgbnVjbGlvIHNlcnZlcmxlc3MgZnVuY3Rpb24sIG9yIGFzIHBhcnQgb2YgYSByZWFsLXRpbWUgcGlwZWxpbmUuCiAgICAiIiIKCiAgICBkZWYgbG9hZChzZWxmKToKICAgICAgICAiIiIKICAgICAgICBsb2FkcyBhbiBtb2RlbCB0aGF0IHdhcyBsb2dnZWQgYnkgdGhlIE1MRmxvdyB0cmFja2VyIG1vZGVsCiAgICAgICAgIiIiCiAgICAgICAgIyBVbnppcCB0aGUgbW9kZWwgZGlyIGFuZCB0aGVuIHVzZSBtbGZsb3cncyBsb2FkIGZ1bmN0aW9uCiAgICAgICAgbW9kZWxfZmlsZSwgXyA9IHNlbGYuZ2V0X21vZGVsKCIuemlwIikKICAgICAgICBtb2RlbF9wYXRoX3VuemlwID0gbW9kZWxfZmlsZS5yZXBsYWNlKCIuemlwIiwgIiIpCgogICAgICAgIHdpdGggemlwZmlsZS5aaXBGaWxlKG1vZGVsX2ZpbGUsICJyIikgYXMgemlwX3JlZjoKICAgICAgICAgICAgemlwX3JlZi5leHRyYWN0YWxsKG1vZGVsX3BhdGhfdW56aXApCgogICAgICAgIHNlbGYubW9kZWwgPSBtbGZsb3cucHlmdW5jLmxvYWRfbW9kZWwobW9kZWxfcGF0aF91bnppcCkKCiAgICBkZWYgcHJlZGljdChzZWxmLCByZXF1ZXN0OiBEaWN0W3N0ciwgQW55XSkgLT4gbGlzdDoKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgaW5wdXRzIHRocm91Z2ggdGhlIG1vZGVsLiBUaGUgaW5mZXJyZWQgZGF0YSB3aWxsCiAgICAgICAgYmUgcmVhZCBmcm9tIHRoZSAiaW5wdXRzIiBrZXkgb2YgdGhlIHJlcXVlc3QuCgogICAgICAgIDpwYXJhbSByZXF1ZXN0OiBUaGUgcmVxdWVzdCB0byB0aGUgbW9kZWwgdXNpbmcgeGdib29zdCdzIHByZWRpY3QuCiAgICAgICAgICAgICAgICBUaGUgaW5wdXQgdG8gdGhlIG1vZGVsIHdpbGwgYmUgcmVhZCBmcm9tIHRoZSAiaW5wdXRzIiBrZXkuCgogICAgICAgIDpyZXR1cm46IFRoZSBtb2RlbCdzIHByZWRpY3Rpb24gb24gdGhlIGdpdmVuIGlucHV0LgogICAgICAgICIiIgoKICAgICAgICAjIEdldCB0aGUgaW5wdXRzIGFuZCBzZXQgdG8gYWNjZXB0ZWQgdHlwZToKICAgICAgICBpbnB1dHMgPSBwZC5EYXRhRnJhbWUocmVxdWVzdFsiaW5wdXRzIl0pCgogICAgICAgICMgUHJlZGljdCB1c2luZyB0aGUgbW9kZWwncyBwcmVkaWN0IGZ1bmN0aW9uOgogICAgICAgIHByZWRpY3Rpb25zID0gc2VsZi5tb2RlbC5wcmVkaWN0KGlucHV0cykKCiAgICAgICAgIyBSZXR1cm4gYXMgbGlzdDoKICAgICAgICByZXR1cm4gcHJlZGljdGlvbnMudG9saXN0KCkKCmZyb20gbWxydW4ucnVudGltZXMgaW1wb3J0IG51Y2xpb19pbml0X2hvb2sKZGVmIGluaXRfY29udGV4dChjb250ZXh0KToKICAgIG51Y2xpb19pbml0X2hvb2soY29udGV4dCwgZ2xvYmFscygpLCAnc2VydmluZ192MicpCgpkZWYgaGFuZGxlcihjb250ZXh0LCBldmVudCk6CiAgICByZXR1cm4gY29udGV4dC5tbHJ1bl9oYW5kbGVyKGNvbnRleHQsIGV2ZW50KQo=
+    - lightgbm
+    - xgboost
+    code_origin: ''
+    origin_filename: ''
+  image: mlrun/mlrun
+  base_image_pull: false
+  default_handler: ''
+  max_replicas: 4
+  disable_auto_mount: false
   min_replicas: 1
   description: Mlflow model server, and additional utils.
-  max_replicas: 4
-  source: ''
-  function_kind: serving_v2
+  function_handler: mlflow-utils-nuclio:handler
   env:
   - name: MLRUN_HTTPDB__NUCLIO__EXPLICIT_ACK
     value: enabled
-verbose: false
+metadata:
+  categories:
+  - model-serving
+  - utils
+  name: mlflow-utils
+  tag: ''
 kind: serving
 
         
diff --git a/functions/master/mlflow_utils/latest/static/item.html b/functions/master/mlflow_utils/latest/static/item.html
index 27ea1430..d193c1fa 100644
--- a/functions/master/mlflow_utils/latest/static/item.html
+++ b/functions/master/mlflow_utils/latest/static/item.html
@@ -30,9 +30,8 @@
         
 apiVersion: v1
 categories:
-- genai
 - model-serving
-- machine-learning
+- utils
 description: Mlflow model server, and additional utils.
 doc: ''
 example: mlflow_utils.ipynb
@@ -43,7 +42,7 @@
   author: zeevr
 maintainers: []
 marketplaceType: ''
-mlrunVersion: 1.7.0-rc17
+mlrunVersion: 1.8.0
 name: mlflow_utils
 platformVersion: ''
 spec:
@@ -58,7 +57,7 @@
   - lightgbm
   - xgboost
 url: ''
-version: 1.0.0
+version: 1.1.0
 
         
     
diff --git a/functions/master/mlflow_utils/latest/static/mlflow_utils.html b/functions/master/mlflow_utils/latest/static/mlflow_utils.html index fb26fe56..d3e1f984 100644 --- a/functions/master/mlflow_utils/latest/static/mlflow_utils.html +++ b/functions/master/mlflow_utils/latest/static/mlflow_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/1.1.0/static/documentation.html b/functions/master/model_server/1.1.0/static/documentation.html index 10e23680..79a8b873 100644 --- a/functions/master/model_server/1.1.0/static/documentation.html +++ b/functions/master/model_server/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/1.1.0/static/example.html b/functions/master/model_server/1.1.0/static/example.html index e5f17b08..c4e128a8 100644 --- a/functions/master/model_server/1.1.0/static/example.html +++ b/functions/master/model_server/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/1.1.0/static/model_server.html b/functions/master/model_server/1.1.0/static/model_server.html index 88bfe16f..35b760c8 100644 --- a/functions/master/model_server/1.1.0/static/model_server.html +++ b/functions/master/model_server/1.1.0/static/model_server.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/latest/static/documentation.html b/functions/master/model_server/latest/static/documentation.html index 10e23680..79a8b873 100644 --- a/functions/master/model_server/latest/static/documentation.html +++ b/functions/master/model_server/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/latest/static/example.html b/functions/master/model_server/latest/static/example.html index e5f17b08..c4e128a8 100644 --- a/functions/master/model_server/latest/static/example.html +++ b/functions/master/model_server/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server/latest/static/model_server.html b/functions/master/model_server/latest/static/model_server.html index 88bfe16f..35b760c8 100644 --- a/functions/master/model_server/latest/static/model_server.html +++ b/functions/master/model_server/latest/static/model_server.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/1.1.0/static/documentation.html b/functions/master/model_server_tester/1.1.0/static/documentation.html index bea3c892..d3ddf180 100644 --- a/functions/master/model_server_tester/1.1.0/static/documentation.html +++ b/functions/master/model_server_tester/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/1.1.0/static/example.html b/functions/master/model_server_tester/1.1.0/static/example.html index 1cba1254..a3a90484 100644 --- a/functions/master/model_server_tester/1.1.0/static/example.html +++ b/functions/master/model_server_tester/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/1.1.0/static/model_server_tester.html b/functions/master/model_server_tester/1.1.0/static/model_server_tester.html index c3de7a49..ed2fa10f 100644 --- a/functions/master/model_server_tester/1.1.0/static/model_server_tester.html +++ b/functions/master/model_server_tester/1.1.0/static/model_server_tester.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/latest/static/documentation.html b/functions/master/model_server_tester/latest/static/documentation.html index bea3c892..d3ddf180 100644 --- a/functions/master/model_server_tester/latest/static/documentation.html +++ b/functions/master/model_server_tester/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/latest/static/example.html b/functions/master/model_server_tester/latest/static/example.html index 1cba1254..a3a90484 100644 --- a/functions/master/model_server_tester/latest/static/example.html +++ b/functions/master/model_server_tester/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/model_server_tester/latest/static/model_server_tester.html b/functions/master/model_server_tester/latest/static/model_server_tester.html index c3de7a49..ed2fa10f 100644 --- a/functions/master/model_server_tester/latest/static/model_server_tester.html +++ b/functions/master/model_server_tester/latest/static/model_server_tester.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/noise_reduction/1.1.0/src/data/test_data.mp3 b/functions/master/noise_reduction/1.1.0/src/data/test_data.mp3 new file mode 100644 index 00000000..a330f980 Binary files /dev/null and b/functions/master/noise_reduction/1.1.0/src/data/test_data.mp3 differ diff --git a/functions/master/noise_reduction/1.1.0/src/data/test_data.wav b/functions/master/noise_reduction/1.1.0/src/data/test_data.wav new file mode 100644 index 00000000..a3a993c2 Binary files /dev/null and b/functions/master/noise_reduction/1.1.0/src/data/test_data.wav differ diff --git a/functions/master/noise_reduction/1.1.0/src/function.yaml b/functions/master/noise_reduction/1.1.0/src/function.yaml new file mode 100644 index 00000000..d6d33b8d --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/function.yaml @@ -0,0 +1,179 @@ +spec: + entry_points: + reduce_noise: + has_kwargs: false + name: reduce_noise + has_varargs: false + doc: 'Reduce noise from audio file or directory containing audio files. + + The audio files must be in .wav format. + + The cleaned audio files will be saved in the target_directory. + + For information about the noise reduction algorithm see: + + https://github.com/timsainb/noisereduce + + Notice that the saved files are in wav format, even if the original files + are in other format.' + parameters: + - name: audio_source + type: str + doc: path to audio file or directory containing audio files + - name: target_directory + type: str + doc: path to directory to save the cleaned audio files. + - name: sample_rate + type: int + doc: Number of samples in one second in the audio file. Pass `None` to keep + the original sample rate. + default: 16000 + - name: duration + type: int + doc: Duration of the audio file to clean in seconds. Pass `None` to keep the + original duration. + default: null + - name: channel + type: int + doc: Channel to clean. Pass the number of the channel to clean. To clean all + channels pass None. + default: null + - name: silence_threshold + type: float + doc: The threshold to remove silence from the audio, in dB. If None, no silence + removal is performed. + default: null + - name: use_multiprocessing + type: int + doc: Number of processes to use for cleaning the audio files. If 0, no multiprocessing + is used. + default: 0 + - name: verbose + type: bool + doc: Verbosity level. If True, display progress bar. + default: true + lineno: 388 + clean_audio: + has_kwargs: false + name: clean_audio + has_varargs: false + outputs: + - type: torch.Tensor + doc: '' + parameters: + - name: self + - name: data + type: Tensor + lineno: 276 + save_audio: + has_kwargs: false + name: save_audio + has_varargs: false + doc: '' + parameters: + - name: self + - name: audio + type: ndarray + - name: target_path + type: Path + lineno: 256 + load_audio: + has_kwargs: false + name: load_audio + has_varargs: false + outputs: + - type: torch.Tensor + doc: '' + parameters: + - name: self + - name: file + type: str + lineno: 268 + update_to_wav_suffix: + has_kwargs: false + name: update_to_wav_suffix + has_varargs: false + doc: '' + parameters: + - name: self + - name: audio_file + type: Path + lineno: 125 + remove_silence: + has_kwargs: false + name: remove_silence + has_varargs: false + outputs: + - doc: The audio without silence. + doc: Remove silence sections from the audio. + parameters: + - name: self + - name: audio + type: ndarray + doc: The audio to remove silence from. + lineno: 134 + reduce_noise_dfn: + has_kwargs: true + name: reduce_noise_dfn + has_varargs: false + doc: 'Reduce noise from audio files using DeepFilterNet. + + For more information about the noise reduction algorithm see: + + https://github.com/Rikorose/DeepFilterNet + + Notice that the saved files are in wav format, even if the original files + are in other format.' + parameters: + - name: audio_source + type: str + doc: path to audio file or directory of audio files + - name: target_directory + type: str + doc: path to target directory to save cleaned audio files + - name: pad + type: bool + doc: whether to pad the audio file with zeros before cleaning + default: true + - name: atten_lim_db + type: int + doc: maximum attenuation in dB + default: null + - name: silence_threshold + type: float + doc: the threshold to remove silence from the audio, in dB. If None, no silence + removal is performed. + default: null + - name: use_multiprocessing + type: int + doc: Number of processes to use for cleaning the audio files. If 0, no multiprocessing + is used. + default: 0 + - name: verbose + type: bool + doc: verbosity level. If True, display progress bar and logs. + default: true + lineno: 322 + build: + code_origin: '' + base_image: mlrun/mlrun + requirements: + - librosa + - noisereduce + - deepfilternet + - torchaudio>=2.1.2 + functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK + origin_filename: '' + description: Reduce noise from audio files + command: '' + image: '' + default_handler: reduce_noise + disable_auto_mount: false +metadata: + name: noise-reduction + tag: '' + categories: + - data-preparation + - audio +kind: job +verbose: false diff --git a/functions/master/noise_reduction/1.1.0/src/item.yaml b/functions/master/noise_reduction/1.1.0/src/item.yaml new file mode 100644 index 00000000..f748d558 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/item.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +categories: + - data-preparation + - audio +description: Reduce noise from audio files +doc: '' +example: noise_reduction.ipynb +generationDate: 2024-03-04:17-30 +hidden: false +icon: '' +labels: + author: yonatans +maintainers: [] +mlrunVersion: 1.7.0 +name: noise-reduction +platformVersion: 3.5.3 +spec: + filename: noise_reduction.py + handler: reduce_noise + image: mlrun/mlrun + kind: job + requirements: [ + librosa, + noisereduce, + deepfilternet, + torchaudio>=2.1.2, + ] +url: '' +version: 1.1.0 \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/src/noise_reduction.ipynb b/functions/master/noise_reduction/1.1.0/src/noise_reduction.ipynb new file mode 100644 index 00000000..e4fa0a53 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/noise_reduction.ipynb @@ -0,0 +1,942 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4e0abc60-b718-4f45-a82a-0b8759f19d3f", + "metadata": {}, + "source": [ + "# Noise Reduction\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Introduction](#Introduction)\n", + "2. [Project Setup](#Setting-up-a-project)\n", + "3. [Noise Reduction Techniques](#Noise-Reduction-Techniques)\n", + " 1. [DeepFilterNet](#DeepFilterNet)\n", + " 2. [Spectral Gating](#SpectralGating)" + ] + }, + { + "cell_type": "markdown", + "id": "9af33629-965f-4f73-9e4a-89cc4c3dacf1", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "Noise reduction is a crucial signal processing technique used to enhance the quality of signals by minimizing unwanted or irrelevant noise. This technique finds applications in various fields such as audio processing, image processing, telecommunications, and more. The goal is to extract the useful information from a signal while suppressing undesirable background noise." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f9cd530d-36a7-47b1-96f8-498d338b3a1a", + "metadata": {}, + "outputs": [], + "source": [ + "import mlrun" + ] + }, + { + "cell_type": "markdown", + "id": "c659289f-01f2-4e02-b843-b39cfc0c1d63", + "metadata": {}, + "source": [ + "## Setting up a project\n", + "\n", + "First of all we need to create a project with the `noise-reduction` function" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c4217272-85b8-4af7-afee-bc97c6c73bd9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 15:54:53,561 [info] Project loaded successfully: {'project_name': 'noise-reduction'}\n" + ] + } + ], + "source": [ + "# Creating a project\n", + "project = mlrun.get_or_create_project(\"noise-reduction\")\n", + "# Importing the function from hub\n", + "noise_reduction_function = project.set_function(\"hub://noise_reduction\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f7df4c3e-4e5b-47bd-a298-527d9c6fcb8f", + "metadata": {}, + "outputs": [], + "source": [ + "# Audio source can be either a single file or a directory of audio files\n", + "audio_source = \"data\"" + ] + }, + { + "cell_type": "markdown", + "id": "6c1c5109-6380-4364-b016-728523ed0ea1", + "metadata": {}, + "source": [ + "## Noise Reduction Techniques" + ] + }, + { + "attachments": { + "e48ce103-14f3-421d-82a4-823344895241.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "5c81ecee-851c-4ee8-ad3a-4d372a1bfd97", + "metadata": {}, + "source": [ + "\n", + "### 1. DeepFilterNet\n", + "![image.png](attachment:e48ce103-14f3-421d-82a4-823344895241.png)\n", + "\n", + "In order to use this technique, you simply need to use the `reduce_noise_dfn` handler.\n", + "\n", + "Reduce noise from audio files using DeepFilterNet. For more information about the noise reduction algorithm, see [DeepFilterNet GitHub](https://github.com/Rikorose/DeepFilterNet). Notice that the saved files are in wav format, even if the original files are in other formats.\n", + "\n", + "### Parameters:\n", + "\n", + "- `audio_source`: path to the audio file or directory of audio files\n", + "- `target_directory`: path to the target directory to save cleaned audio files\n", + "- `pad`: whether to pad the audio file with zeros before cleaning\n", + "- `atten_lim_db`: maximum attenuation in dB\n", + "- `silence_threshold`: the threshold to remove silence from the audio, in dB. If None, no silence removal is performed.\n", + "- `use_multiprocessing`: Number of processes to use for cleaning the audio files. If 0, no multiprocessing is used.\n", + "- `verbose`: verbosity level. If True, display progress bar and logs.\n", + "- `kwargs`: additional arguments to pass to `torchaudio.load()`. For more information, see [torchaudio.load()](https://pytorch.org/audio/stable/generated/torchaudio.load.html).\n", + "\n", + "\n", + "In the examples below, the function is running locally, for running remotely, it is required to build the function's image first (need to execute only once):\n", + "```python\n", + "noise_reduction_function.apply(mlrun.auto_mount()) # required for local files\n", + "project.build_function(\"noise-reduction\")\n", + "```\n", + "\n", + "#### 1.1. Example" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "16113524-8597-48d4-8172-76b897fee3f2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 15:54:56,999 [info] Storing function: {'name': 'noise-reduce-reduce-noise-dfn', 'uid': '9732dac831784a6a8b53acab5ff83a08', 'db': 'http://mlrun-api:8080'}\n", + "> 2024-03-04 15:55:07,525 [info] logging run results to: http://mlrun-api:8080\n", + "> 2024-03-04 15:55:07,702 [info] Reducing noise from audio files.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Noise-reduction: 0%| | 0/2 [00:00 2024-03-04 15:55:08,437 [info] Loading DeepFilterNet2 model.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`torchaudio.backend.common.AudioMetaData` has been moved to `torchaudio.AudioMetaData`. Please update the import path.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-03-04 15:55:08 | INFO | DF | Running on torch 2.1.2+cu121\n", + "2024-03-04 15:55:08 | INFO | DF | Running on host jupyter-yoni-d56767c87-678n2\n", + "> 2024-03-04 15:55:08,464 [info] Loading DeepFilterNet2 model.\n", + "2024-03-04 15:55:08 | INFO | DF | Running on torch 2.1.2+cu121\n", + "2024-03-04 15:55:08 | INFO | DF | Running on host jupyter-yoni-d56767c87-678n2\n", + "2024-03-04 15:55:08 | INFO | DF | Loading model settings of DeepFilterNet3\n", + "2024-03-04 15:55:08 | INFO | DF | Using DeepFilterNet3 model at /igz/.cache/DeepFilterNet/DeepFilterNet3\n", + "2024-03-04 15:55:08 | INFO | DF | Initializing model `deepfilternet3`\n", + "2024-03-04 15:55:08 | INFO | DF | Loading model settings of DeepFilterNet3\n", + "2024-03-04 15:55:08 | INFO | DF | Using DeepFilterNet3 model at /igz/.cache/DeepFilterNet/DeepFilterNet3\n", + "2024-03-04 15:55:08 | INFO | DF | Initializing model `deepfilternet3`\n", + "2024-03-04 15:55:08 | INFO | DF | Found checkpoint /igz/.cache/DeepFilterNet/DeepFilterNet3/checkpoints/model_120.ckpt.best with epoch 120\n", + "2024-03-04 15:55:08 | INFO | DF | Found checkpoint /igz/.cache/DeepFilterNet/DeepFilterNet3/checkpoints/model_120.ckpt.best with epoch 120\n", + "2024-03-04 15:55:08 | INFO | DF | Running on device cpu\n", + "2024-03-04 15:55:08 | INFO | DF | Running on device cpu\n", + "2024-03-04 15:55:08 | INFO | DF | Model loaded\n", + "2024-03-04 15:55:08 | INFO | DF | Model loaded\n", + "> 2024-03-04 15:55:08,635 [info] Reducing noise from test_data.mp3.\n", + "> 2024-03-04 15:55:08,636 [info] Reducing noise from test_data.wav.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2024-03-04 15:55:08\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mDF\u001b[0m | \u001b[33m\u001b[1mAudio sampling rate does not match model sampling rate (16000, 48000). Resampling...\u001b[0m\n", + "\"sinc_interpolation\" resampling method name is being deprecated and replaced by \"sinc_interp_hann\" in the next release. The default behavior remains unchanged.\n", + "The MPEG_LAYER_III subtype is unknown to TorchAudio. As a result, the bits_per_sample attribute will be set to 0. If you are seeing this warning, please report by opening an issue on github (after checking for existing/closed ones). You may otherwise ignore this warning.\n", + "\u001b[32m2024-03-04 15:55:08\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mDF\u001b[0m | \u001b[33m\u001b[1mAudio sampling rate does not match model sampling rate (16000, 48000). Resampling...\u001b[0m\n", + "\"sinc_interpolation\" resampling method name is being deprecated and replaced by \"sinc_interp_hann\" in the next release. The default behavior remains unchanged.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 15:55:16,701 [info] Saved cleaned audio file to clean_data/test_data.wav.\n", + "> 2024-03-04 15:55:16,706 [info] Saved cleaned audio file to clean_data/test_data_mp3.wav.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Noise-reduction: 100%|██████████| 2/2 [00:09<00:00, 4.51s/file]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 15:55:16,791 [info] Summarizing the results.\n", + "> 2024-03-04 15:55:16,792 [info] Done (2/2)\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
noise-reduction0Mar 04 15:54:57completednoise-reduce-reduce-noise-dfn
v3io_user=yonis
kind=local
owner=yonis
host=jupyter-yoni-d56767c87-678n2
audio_source
target_directory=./clean_data
use_multiprocessing=2
silence_threshold=50
atten_lim_db=10
successes
errors
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 15:55:17,976 [info] Run execution finished: {'status': 'completed', 'name': 'noise-reduce-reduce-noise-dfn'}\n" + ] + } + ], + "source": [ + "dfn_run = noise_reduction_function.run(\n", + " handler=\"reduce_noise_dfn\",\n", + " inputs={\"audio_source\": audio_source},\n", + " params={\n", + " \"target_directory\": \"./clean_data\",\n", + " \"use_multiprocessing\": 2,\n", + " \"silence_threshold\": 50,\n", + " \"atten_lim_db\": 10,\n", + " },\n", + " returns=[\"successes: file\", \"errors: file\"],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a71ba944-1fc2-48be-b789-d57c59201939", + "metadata": {}, + "source": [ + "### Looking at the result" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "19b04cf6-5a4d-4d74-b66e-193540a900a1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": { + "test_data.mp3": "clean_data/test_data_mp3.wav", + "test_data.wav": "clean_data/test_data.wav" + }, + "text/plain": [ + "" + ] + }, + "metadata": { + "application/json": { + "expanded": false, + "root": "root" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/json": {}, + "text/plain": [ + "" + ] + }, + "metadata": { + "application/json": { + "expanded": false, + "root": "root" + } + }, + "output_type": "display_data" + } + ], + "source": [ + "dfn_run.artifact(\"successes\").show()\n", + "dfn_run.artifact(\"errors\").show()" + ] + }, + { + "attachments": { + "68c16acf-c28e-4bb8-a453-abbebc0137ce.png": { + "image/png": "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2rO6epfhrqU1aI20pmf3uN1te4L2NNQ61G7Zq7u3bB/YJ933fH/a/psHIg40HWQerD1keajyMP1waR1SN62urz6rvqMhpaH9SPiRpkafxsNHHY/uOGZ6rOK4zvGlJygn5p8YOFl0sv+U+FTv6czTT5omNt07M+bM9ebY5tazEWcvnAs5d+Y86/zJC74Xjl30vnjkEvNS/WWPy3Ut7i2Hf3f//XCrR2vdFc8rDW1ebY3to9pPXPW/evpa0LVz1znXL9+IutF+M/Hm7VvjbnXc5t/uvpN759Xdwruf7825T7hf+kDjQflDw4dVf9j+sbfDo+P4o6BHLY/jH997wnvy4mn+0y+d85/RnpV3mXRVd7t0H+sJ6Wl7PvZ55wvxi8+9JX9q/ln50ublob8C/mrpG9PX+UryauD14jf6b3a8dXvb1B/T//Bd3rvP70s/6H/Y+ZH58fyn5E9dn6d8IX1Z+9X2a+O3iG/3B/IGBsRcCVf+K4DBimZkAPB6BwC0FADo8HxGGas4/8kLojizyhH4T1hxRpQXDwBq4f97bC/8u7kFwL5t8PgF9dXHARBDAyDBC6CurkN18KwmP1fKChGeAzbHfk3PSwf/pijOnD/E/XMLZKpu4Of2X5JOfJCem+crAAAAOGVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAACoAIABAAAAAEAAAR6oAMABAAAAAEAAAIoAAAAAIFlXtUAAEAASURBVHgB7J0H3BS11odXBEVRERs2bFixImLFLnbFXrD3ig17ufbee78qdrFivfbesIANO6CgICpFRQTF79k33ny5yezs7O5s/7+/ezGTOTlJnpmdyZmcnEzz999/Z/QnAiIgAiIgAiIgAiIgAiIgAiKQNoFWaSuUPhEQAREQAREQAREQAREQAREQgSwBmVu6D0RABERABERABERABERABESgLARkbpUFq5SKgAiIgAiIgAiIgAiIgAiIgMwt3QMiIAIiIAIiIAIiIAIiIAIiUBYCMrfKglVKRUAEREAEREAEREAEREAEREDmlu4BERABERABERABERABERABESgLAZlbZcEqpSIgAiIgAiIgAiIgAiIgAiIgc0v3gAiIgAiIgAiIgAiIgAiIgAiUhYDMrbJglVIREAEREAEREAEREAEREAERkLmle0AEREAEREAEREAEREAEREAEykJA5lZZsEqpCIiACIiACIiACIiACIiACMjc0j0gAiIgAiIgAiIgAiIgAiIgAmUh0LosWqVUBERABESgsgSGDx/++++/e3V26tSpXbt2XqYOK0Bg6tSpn3/+uVfRNNNMs8QSS3iZOhQBERABEWhsAtP8/fffjd1D9U4EREAEmoHAQgsthMXl9fTLL7/s3Lmzl6nDChAYMmRIly5dvIoWW2yx0AbzZHQoAiIgAiLQYATkTNhgF1TdEQERaEYCY8aMCW2t2WabbZFFFmlGHDXQ54EDB4atWHnllcNM5YiACIiACDQ2AZlbjX191TsREIGmIBA5uO/evTvea03R/9rr5Ntvvx02iisSZipHBERABESgsQnI3Grs66veiYAINAWBXOZWU3S+JjsZaW5pdqsmr5UaJQIiIALlJSBzq7x8pV0EREAEKkBA5lYFICev4o8//hg8eLAn37p16xVWWMHL1KEIiIAIiEDDE5C51fCXWB0UARFocAJEPJK5VVPX+IMPPpg8ebLXpGWWWWaGGWbwMnUoAiIgAiLQ8AQUCL7hL7E6KAIi0OAEMLeeeuopr5Os2ppnnnm8TB1WhkCk9StPwsrAVy0iIAIiUGsEZG7V2hVRe0RABESgMAKtWrXq2rVrYWUkXU4CkQu3FCejnMilWwREQARql4CcCWv32qhlIiACIiAC9UhAs1v1eNXUZhEQAREoEwFtc1wmsFIrAiJQfwReeeWVCRMmuO2eaaaZ1l57bZODzx57W7377rvvvPMO/3799dcLL7zwUksttdlmm2200UZuqbzpKVOmvPbaa3gAshnuDz/8wK5Z008//ZJLLrnEEkuYf5dffvm2bdvm1WMEXn755V9++cUT7tGjR/v27b1Mezhp0iTWF3311Vfsg2z+Ro4cOcccc8w333zzzjvv/PPP36tXL9YaWfmEiT///PP5559/4oknRowY8d13340aNWr22WdHj/lbY4014JlXFbYKTFyxdu3arbPOOibnt99+u+6661566aVPP/10gQUWoJv8rb/++mHIe64XfXzwwQe/+OKL71v+kPlvW5ZZbbXV6KlbS5L0hx9+eMcdd3zyySfffvvtuHHjOnbs2KlTp0022WS77baztLmFZp11Vmp3FbJqi3yiZbiZ6Bk6dKibQ5obINfO1MZrdOrUqV6RjTfeeNppp/UyvcOJEyc+/PDDQOO+5Q8etJxtl/lbbrnl0MAcqVck/pB7+Nlnn+Uettd6rrnmsni51jPOOGO8Bp0VAREQgWYhwONbfyIgAiIgAn/99RejZO/Rv+mmmxoyn332GcN676w93H777Rm/JmH4448/Hn300TPPPLMtG5lg5HruuecyQM+rEwsnUtvYsWMjy9KA0047Df2R9bqZ66233iOPPAKWSD1eJpbVwQcfjMHmavDSCy20EMaYVzA8xIL1CtqrcNddd2HheGcxdz0lBAY8//zzsV09SfcQ++eKK65I2Dv0v/766yuttJKrwU1jG19yySVGG310T5k0N4/XSA6xaUPJ+++/P5Q0Odh4oTwr9DDAchUhn1LHH388e16HZW1Ot27dsGBjlLinsK/233//eIWLLrooHy/cUkqLgAiIQNMSyDRtz9VxERABEXAJYFDZ0adN/Otf/8KeufDCC/PONTEW53u/q9BLMya+/PLLZ5llFqs8b2LxxRf/6KOPPD3e4ccffxzqoaAnZg4ZATN/FcrH5GBExY/mOcuET/zg29V/yCGHMBcX2Twyx48fH85TnXrqqdRy3nnnuXps2hvWM/fIdI09G59g6pIpvlyNMfncAGeeeWbe6SMq2nLLLQlIiKUXVnrkkUeGtUTGMvnmm29CSZPzwAMPhJqpNJc8N+QRRxyRpOVGLV8NsFRzaSOfq/Dvf//bzuOFjXFzuI70mtnIGIU6JQIiIALNQEDmVjNcZfVRBEQgPwFsBnewaNIM8ZMHlLvoootyVcMofJ999gn1583Bjy7e4rr11ltDJbvuumvYEly/ko+8XZ0MmnNZXMzn7Lnnnq5wknSMhRA5NfT4448z1xepGac1t6e33HJLoX1kugwbz1XipQ877LDIqiMz+/Tps+2224anmJfz1DJHFIrRmFyoKX7ccceFRc466yxPszn86aef8LEM5eNzDj300EhtZGJ27rTTTvHFw7M77rhjLoXKFwEREIEmISBzq0kutLopAiKQh0BBo+pwWEkOS54i62CcijtcZJEkmaussgoaIjWTyexTqIRpNE8eH8KYpUqsHAuVuDkYPJ5CDjEMsMRcseTpSIXojJzCuvfee3OtLHr00Udtw3B9zCUW37DIqSejFpLxZcOz0003XZjJAjnbTpNgUVkotvnmm3ti7iHunWGR//znP66MSTNViztfKJwk58477wwVcq0PPPDA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gAg0MwGZW8189dV3ERABERABERABERABERCBMhKQuVVGuFItAiIgAiIgAiIgAiIgAiLQzARkbjXz1VffRUAEREAEREAEREAEREAEykhA5lYZ4Uq1CIiACIiACIiACIiACIhAMxOQudXMV199FwEREAEREAEREAEREAERKCMBmVtlhCvVIiACIiACIiACIiACIiACzUxA5lYzX331XQREQAREQAREQAREQAREoIwEZG6VEa5Ui4AIiIAIiIAIiIAIiIAINDMBmVvNfPXVdxEQAREQAREQAREQAREQgTISkLlVRrhSLQIiIAIiIAIiIAIiIAIi0MwEWjdz59V3ERABERCB8hH4+++/R40a9dVXXw0bNuznn38eN27c2LFj+dcmxo8fj8y0LX+tWrUyCfPvTDPN1LFjx7nmmot/3QTpGWecsXxtlmYREAEREAERSJeAzK10eUqbCIiACDQpgZ9++um999774osvsK/M39dffz1x4sTUccw333xLO39dunSZZZZZUq9FCkVABERABEQgFQLT8GUxFUVSIgIiIAIi0FQEfvvtN+yrt99+e+DAgfw7dOjQanW/U6dO2F/LLrtsjx491lxzzQ4dOlSrJapXBERABERABDwCMrc8IDoUAREQARHISWDSpEnPP//8Y4899sorr3zyySdTp07NKVqlE9NMM03Xrl3XWWedddddF9Orffv2VWqIqhUBERABERCBLAGZW7oPREAEREAE8hBgzdXjjz/+yCOPPPXUU7/++mse6Zo5zXowTC/sLv7WW2+9tm3b1kzT1BAREAEREIFmISBzq1mutPopAiIgAoUSGD58OCbWww8//PLLL//111+FFq8p+ZlnnrlXr1477LDDhhtuOP3009dU29QYERABERCBBiYgc6uBL666JgIiIAIFE2BB76BBg4yVNXjw4ILL13wB4mpstdVW2F09e/acbrrpar69aqAIiIAIiEB9E5C5Vd/XT60XAREQgVQIsAqLKawHHnhgwIAB33zzTSo6a1zJrLPOau2u1q0Vp7fGL5eaJwIiIAL1SkDmVr1eObVbBERABFIhQIDBfv36XX755Z999lkqCutOyfzzz3/QQQftt99+c845Z901Xg0WAREQARGocQIyt2r8Aql5IiACIlAuAiNGjLjqqqtuuOEGImGUq4760Ytj4U477dSnT5+VVlqpflqtloqACIiACNQ6AZlbtX6F1D4REAERSJ0A22Rddtll/fv3//PPP1NXXu8KV1llFYyu7bbbThE16v1Sqv0iIAIiUAsEZG7VwlVQG0RABESgEgQwrh566CEMrddff70S9dVzHR07dtx///1xMpxnnnnquR9quwiIgAiIQJUJyNyq8gVQ9SIgAiJQAQLjxo276aabrrzyyuqGwcBhr0OHDsSoMP+SIIcQ8wTq4F/7N3ny5DFjxvzwww+jR4+eMmVKBfjkqoKtug4++ODjjjturrnmyiWjfBEQAREQARGIISBzKwaOTomACIhA3RMYOXLkBRdccPPNNxMSo2KdwY7q/L9/iyyyCIEoZphhhoLaQFR6DEWMLmN6YSt+/N+/SnanXbt2hx9+eN++fWebbbaC2i9hERABERABEZC5pXtABERABBqTwK+//nphy9/vv/9e7h7iete95W/llVfu1q1buUP8MRvmml4DBw4cMmRIufvIhl1YXEcccQSJctcl/SIgAiIgAg1DQOZWw1xKdUQEREAE/iGAVx6x3U866aTvv/++fFAWWmihLbfccu2118bOIpb6NNNMU7668mpmBuyll156seWvrKYXE1zHHnvsoYceypRX3lbFC/zU8rf44ovHi+msCIiACIhAXROQuVXXl0+NFwEREAGfwAsvvHDUUUcNGjTIP5HS8YorrsjuwL169Vp22WWra2Ll6tCoUaOM6fXUU08NGzYsl1gp+SzlOvHEEwmkwdqz4vRMmjRpgw02WHrppa+//vriNKiUCIiACIhAXRCQuVUXl0mNFAEREIH8BD7//PNjjjlmwIAB+UULlGjduvU666yDlcV0VqdOnQosXTVxln69++6797X8DR8+PPV2LLXUUtdccw1kCtWMMyR7fBGInymy7777Tt6JhQKUvAiIgAjUEQGZW3V0sdRUERABEYgm8PPPP59xxhlXX311uvtozTzzzJtuuikTWZtssgnRL6Lrrodc7C7Wdxm769tvv023ybvssstFF10099xzJ1d79NFHX3zxxUaeq0bww+RlJSkCIiACIlBfBGRu1df1UmtFQARE4H8IEDOdCRZsrbFjx/7PiRIO2N63d+/eO+64I/M2DbbVL9NKbPF811133XbbbRMmTCgB0v8UZXrq7LPPxrdw2mmn/Z8TUQeE4z/ssMPsGXwyBw8eXJtumbaRSoiACIiACBRNQOZW0ehUUAREQASqTIBlWgcccMAXX3yRVjsIMHjIIYegs+G3mSJs4x133HHVVVcRWD4tel27dr322mtXWWWVGIUPP/zwNttsw2ybK/Pqq6+uscYabo7SIiACIiACDUNA5lbDXEp1RAREoIkITJw48YQTTrjiiivS6vMKK6xw5JFHMqPVYNNZ8Xwwe4hlyHTTI488wsRXvHCSs0xS7bfffueee27kDl1vvvnmuuuuS5AMT9Wuu+56++23e5k6FAEREAERaAwCMrca4zqqFyIgAk1E4I033thjjz1SmdTCPCD6BXtJEc+9mf3Z2MWLiakbb7yR2Oyl30lzzDEHS7N22203F+lXX3216qqr/vjjj6F+whuyGzWlwlPKEQEREAERqHcCreq9A2q/CIiACDQPgT/++INJrR49epRuaxETr0+fPgQzxL2NNVquYdA8PG1PF1hgAaakRowYwUxXQUEvrAY3gU2FPbz99ttb440cwo1E2loUZAHerbfe6mpQWgREQAREoGEIaHarYS6lOiICItDgBN5///3dd9/9o48+KrGfRHJnOmvvvfeu62CDJUKIKY6jJjNd5513Xi7rKKasd2reeeclJgfrstZff33mJL2z7mHnzp0xfVu10jdQl4rSIiACItAIBGRuNcJVVB9EQAQam8CUKVMY/RN+sMQ47wR2P/nkkwmL17Zt28YmVnrvfvnlF2a6LrzwwnHjxpWobdFFF/3yyy/zKnn66ad79uyZV0wCIiACIiAC9UVA5lZ9XS+1VgREoOkIfPLJJ3imvfPOO6X0nGmT/fff//TTT2/4kIOlUArLYmtdcskll156KZEMw7Pp5my99dYPPvhgujqlTQREQAREoOoEZG5V/RKoASIgAiIQTYC4eZdffvnxxx/Pkq1oiWS5G2+8MfvwLr300snEJeUTwKvw/PPP51owzeifS++YPbuGDx8+33zzpadSmkRABERABKpPQG7i1b8GaoEIiIAIhASYTiEsO8HZS7G1unTp8mTLn2ytkHDyHGIG4lX44YcfsgQrealCJf/666+bbrqp0FKSFwEREAERqHECmt2q8Quk5omACDQjAaIm4FqGG2HRnZ9zzjlZ67Xvvvu2bt26aCUq6BFgvvG+++7DBv7++++9U6kcMrU1bNgwXbJUYEqJCIiACNQIAc1u1ciFUDNEQARE4B8Cjz76aPfu3Yu2tdjE6bjjjiNS/IEHHqiBe7p3FeHymXL89NNPCe1YjiiC7L712GOPpdtmaRMBERABEaguAZlb1eWv2kVABETg/wlMnTr11FNPZd/hCRMm/H9uISk20v3ggw8IY9i+fftCykm2AAKzzDILwTPefffd1VZbrYBiyUSvu+66ZIKSEgEREAERqA8Cciasj+ukVoqACDQ8gbFjx+66665PPPFEcT1t06YN3oNHH320ZrSKA1hEKcxjtic+9thj7XbGRSgJixA1nm24wnzliIAIiIAI1CMBzW7V41VTm0VABBqNAGEYcCAs2tZafvnliRRPDEPZWpW8M/AnZLfoIUOG9OrVK8V6b7jhhhS1SZUIiIAIiEB1CWh2q7r8VbsIiIAIZO69915G7RMnTiyCBdHDTzjhhFNOOYUlW0UUV5FUCBBC48YbbzzkkENK3IfaNIZAiCNGjJh++ulTaZuUiIAIiIAIVJeAZreqy1+1i4AINDUBhunnnnvuTjvtVJytteSSS77++utnnnmmbK3q3kaE0CBwfyq2Fh1hm68HHniguj1S7SIgAiIgAmkRkLmVFknpEQEREIHCCLDPUp8+fU488cTCirVIM74/6qij3nvvvZVXXrmI4iqSLoH+/fv37ds3RZ3XXnttitqkSgREQAREoIoE5ExYRfiqWgREoHkJTJo0icAYxU1iLLDAAv369Vt77bWbF18t9fy1115j++NSdqOO7A3L+ZZZZpnIU8oUAREQARGoIwKa3aqji6WmioAINAiBcePGbbTRRsXZWhtssAEhyGVr1cit8NlnnxG4P3Vbi94pInyNXGI1QwREQARKJKDZrRIBqrgIiIAIFEaAKAibbLLJRx99VFixFmn2Lz777LMJj1FEWRVJncDo0aPZemvo0KGpa0bhzDPP/N13380000zlUC6dIiACIiACFSOg2a2KoVZFIiACIpD55JNPGKAXYWsx7L7//vvZv1i2Vo3cRr/99tsWW2xRJluLPv7yyy933313jXRWzRABERABESiagMytotGpoAiIgAgURoBFPj169GB2q7Bimcziiy/+1ltvbbvttoUWlHyZCBDmpHfv3gMHDiyTfqOWgBnErixrFVIuAiIgAiJQbgIyt8pNWPpFQAREIEuAiO2s1xo7dmyhONhC9+233+7SpUuhBSVfJgKYQIcddtiAAQPKpN+qff/998tt0dm6lBABERABESgTAZlbZQIrtSIgAiLw/wTeeecd1mvhfvb/WQlSRHs/66yzHnzwwfbt2ycQl0iFCDDT+NBDD1WmMgXMqAxn1SICIiAC5SOgUBnlYyvNIiACIpAl8MEHH6yzzjqFzmu1a9fuvvvu23TTTQWxBgkwwfXpp58+1/L3wgsvjB8/vkyNnGGGGUaOHNmhQ4cy6ZdaERABERCBchOQuVVuwtIvAiLQ1ASGDBlC0PYxY8YURGGuueZ64oknunXrVlApCVeFAOu42G/amF6vvvoqO6ql24zLLrvs8MMPT1entImACIiACFSMgMytiqFWRSIgAk1H4Msvv1xrrbW+//77gnq+6KKLPvXUU507dy6olIRrgQC21htvvGFML5ZdYYmV3qoll1ySgJZ4lpauShpEQAREQAQqT0DmVuWZq0YREIGmIDB8+HBsrW+++aag3nbv3v2xxx5jdqugUhKuQQITJkx46aWXjOlVROh/t0f4K+KP6uYoLQIiIAIiUC8EZG7Vy5VSO0VABOqJAOttsLW+/vrrghq98cYb9+/fXzvbFgStLoTZEPn55583ptewYcMKbfMOO+xw7733FlpK8iIgAiIgArVAQOZWLVwFtUEERKChCDC2Zr3WZ599VlCv9thjjxtvvLFNmzYFlZJw3RHACDd2FwZYwkV9rVu3Zru2jh071l1n1WAREAEREAGZW7oHREAERCBNAj/99NO666774YcfFqT0hBNOOPvss7U+pyBo9S48depUnAyN6YXb4a+//hrTI26PE088MUZAp0RABERABGqTgMyt2rwuapUIiEBdEhg3btz6669PnLqCWn/yySefeeaZBRWRcIMRmDJlCqE1jOlFsI3Jkyd7HVxwwQW/+uqraaed1svXoQiIgAiIQI0TkLlV4xdIzRMBEagbAsxO9OzZ88033yyoxX379r3wwgs1r1UQtMYWnjhxIgHljemF6c4eX6a/xFDZbLPNGrvv6p0IiIAINB4BmVuNd03VIxEQgSoQwDFsm222eeSRRwqq+5BDDrnyyitlaxUEramEf/755xdffNGYXostttijjz7aVN1XZ0VABESgAQjI3GqAi6guiIAIVJ8Ai6/OO++8gtqxzz773HDDDa1atSqolISblgAbuM0zzzxN2311XAREQATqlIDMrTq9cGq2CIhADRG4/fbbd99994Ia1Lt37379+mkpTkHQJCwCIiACIiACdUdA5lbdXTI1WAREoLYIvP7664QiDGMbxLRy2223veeee4juHSOjUyIgAiIgAiIgAg1AQOZWA1xEdUEERKBqBAhFuMQSS/zwww/JW7D55ps/8MAD0003XfIikhQBERABERABEahTAlozUKcXTs0WARGoCQIEuijI1iJ0Yf/+/WVr1cTFUyNEQAREQAREoPwENLtVfsaqQQREoEEJ4EBI6AJixyXs31prrfXkk0/OOOOMCeUlJgIiIAIiIAIiUO8ENLtV71dQ7RcBEagagZ9++im5rbXqqquyb5JsrapdLVUsAiIgAiIgAtUgIHOrGtRVpwiIQEMQYOFWwn4svvjiTzzxxMwzz5xQXmIiIAIiIAIiIAKNQUDmVmNcR/VCBESgCgQ6duyYJLrgrLPOyu60HTp0qEITVaUIiIAIiIAIiEBVCcjcqip+VS4CIlDPBGabbbaNNtoovgfsrEVsDGa34sV0VgREQAREQAREoCEJyNxqyMuqTomACFSIwOGHH96qVdyD9Iorrthggw0q1BpVIwIiIAIiIAIiUGME4kYJNdZUNUcEREAEao4Agd2JBR/ZrGmmmebSSy89+OCDI88qUwREQAREQAREoBkIyNxqhqusPoqACJSRAAbVvffeu/TSS7t1LLroovgQHnHEEW6m0iIgAiIgAiIgAs1GQPtuNdsVV39FQATKQmDq1KmvvPLK999/P2HChG7duq244orMbpWlJikVAREQAREQARGoHwLpm1sDBw5kUXj79u3rB4JaKgIiIAIiIAJlJ8C+2G+99VaPHj1kipedtSoQAREQgZohkLIz4Y8//rjyyiuvsMIKNdNBNUQEREAEREAEaoLAYYcdttZaa1188cXXXHPN6NGja6JNaoQIiIAIiECZCbROV//ff/+Nwl9//TVdtdImAiIgAiIgAvVO4Pfff6cLl1xyCU6n48ePP+GEE+q9R2q/CIiACIhAXgIpz27lrU8CIiACIiACItCcBMwXyebsu3otAiIgAk1LoCzmlt4oTXs/qeMiIAIiIALxBMzCrU8//TReTGdFQAREQAQag0BZzK3GQKNeiIAIiIAIiECZCPTr169MmqVWBERABESgpgiUxdzi0x1b0Jx55pnpdvW5554bNGhQujqlTQREQAREQASqQuDPP/+sSr2qVAREQAREoJIEymJu0YFPPvnkX//6V7o92WCDDbp27ZquTmkTAREQAREQgUoSsFHg27Rpw15tlaxadYmACIiACFSeQMrmlnmL2LVb8k2v/BVVjeUgwGztpptuWg7N0ikCItA8BMzL0b4i6fh7773XPN1XT0VABESgOQmkbG65bxGAvv/++82JVb1uMALM1j755JPffvttg/VL3REBEag8ATu7VfmqVaMIiIAIiEDlCaRsbpkO2HdJ7969t9pqq1R69fnnn6eiR0pEoGgCgwcPLrqsCoqACIiACIiACIiACDQhgbKYW+4c1+OPP85q4HHjxpUId9llly1Rg4qLQIkEtthii/vuu69EJVUszv7j+Pf+8ccfVWyDqhYBEXAJDB8+nLBS77zzjpuptAiIgAiIQCMRSNncsvNaLqP11ltv3nnnHTVqlJuptAiUSGDXXXfde++9S1SSt/jhhx/evn17K7bLLrvYdN0ljjnmmKWWWqpnz55113I1WAQalcCll15KWKnu3buX2EG+cvIxaPLkySXqUXEREAEREIHUCaRsboXtY2qLyEu///77lClTwrNF5CiOUxHQGrLInXfeecstt5S1a999990VV1wxYcIEW0tdB242jdcvyF5NJUSgkgT++uuvp59+mhojv0uW2JK77757xx13PPTQQ0vUo+IiIAIiIAKpE0jZ3HLdCFNp6/HHH9+6dWscLexHux9//DEVzVIiAnkJzDfffKHM2LFjw8z6ypl99tnrq8FqrQg0AAHeYj/88EOZOmI+aE6aNKlM+qW27giMHj06rc/cddd3NVgEao1AyuZWzEe74iwxvsTzRfDCCy+04IrTY4sr0RgEpk6dWq2O3HzzzdWqOq16f/7557RUSY8IiEChBEaMGFFokbzyuJDklZFA8xBgpe7cc8+9zjrrVKDLX3zxxR577FHXC5srQElVNDmBlM2t//znP+UAqp9xOajWtc7ddtutWu2v3++FMV9DqgVT9YpA8xB48cUXy9fZgw46qHzKpbnuCGBu0Wa2MKlAy/v379+vXz98Wav4GbQC3VQVIlAKgTTNLVaGEL2glNaorAgkJEDEy4SSqYudddZZqeusjELNDFeGs2oRgUgC5dgqHYexr7/+2lb34IMP2rQSzUxgmWWWofuVeebbWhZccMFmZq6+i0AMgTTNra5du8bUZH+QMTJJ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2Ai9S0ImyhEBERABERABCOgVqdtABERABJqEQH2EymiSi6FuioAIiIAIiIAIiIAIiIAINBIBmVuNdDXVFxEQAREQAREQAREQAREQgRoiIHOrhi6GmiICIiACIiACIiACIiACItBIBGRuNdLVVF9EQAREQAREQAREQAREQARqiEBqgeDDPj366KMTJ04M85UjAiIgAiIgAk1O4O6779bOKE1+D6j7IiACTUKgjJEJm4SguikCIiACIiACIiACIiACIiACkQTkTBiJRZkiIAIiIAIiIAIiIAIiIAIiUCoBmVulElR5ERABERABERABERABERABEYgkIHMrEosyRUAEREAEREAEREAEREAERKBUAjK3SiWo8iIgAiIgAiIgAiIgAiIgAiIQSUDmViQWZYqACIiACIiACIiACIiACIhAqQRkbpVKUOVFQAREQAREQAREQAREQAREIJKAzK1ILMoUAREQAREQAREQAREQAREQgVIJyNwqlaDKi4AIiIAIiIAIiIAIiIAIiEAkAZlbkViUKQIiIAIiIAIiIAIiIAIiIAKlEpC5VSpBlRcBERABERABERABERABERCBSAIytyKxKFMEREAEREAEREAEREAEREAESiUgc6tUgiovAiIgAiIgAiIgAiIgAiIgApEEWkfm5sp8Yrs1p5vun5M//pgZ9Xtm5pajFzKZvRbM/D4xezBkTKZtJmP0Tmg5O/90mVGTs6mpmcwcmYzRQM4CM2VaTZvNHzc+M12bzIgpGSM/byYzfSazSKfsqcHfZjq1y0w/febLn7OHs7fK/DE107FDNv3t2Gwtxl78K5OZsVWmw6yZ33/Pnhr9e6bjDJk2LU2db95M27aZv/7KTJqUPfXp55m/M5kuS2bTUyZn6MW0LW2gIA2baabMlCnZU/PPn+nQIfMXLc5kHn0xM1fbzNSW9IjJmU84m8ksmD2Tocc0daY22fSfUzKt22RemJL5IXuUGZ/JHNE+8+uv2fTff2d+mpr5OpvMUNvcmSw3CvJHUept1dKN3yZlaP4HLfkbzpRZaKFsd4yGEaMzY6i3palzd8y8+V3ms0ym73JZURr26WeZeVCK8haBWWbJpn/4ITNyVObDTKZFKtN2uszPk7MN5m/GTGaWaTPtW8QgA97RYzK3tpxaLJPZuF2mY8fswYgR2QsHZLrP36eTspdvzhmyaSoa+Wu2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IeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGHgEPAIeDULUcDDgGHgEPAIeAQcAg4BBwCDgGHgENgUhBw6takwOoqdQg4BBwCDgGHgEPAIeAQcAg4BBwCTt1yNOAQcAg4BBwCDgGHgEPAIeAQcAg4BCYFAaduTQqsrlKHgEPAIeAQcAg4BBwCDgGH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" + } + }, + "cell_type": "markdown", + "id": "4576b576-4ac0-433a-9d1d-f39225a6648d", + "metadata": {}, + "source": [ + "\n", + "### 2. Spectral Gating\n", + "![image.png](attachment:68c16acf-c28e-4bb8-a453-abbebc0137ce.png)\n", + "\n", + "In order to use this technique, you simply need to use the `reduce_noise` handler.\n", + "\n", + "Spectral gating selectively filters signal frequencies based on amplitude, offering targeted noise reduction or feature enhancement in signal processing applications.\n", + "\n", + "Reduce noise from an audio file or directory containing audio files. The audio files must be in .wav format. The cleaned audio files will be saved in the target directory. For information about the noise reduction algorithm, see [noisereduce GitHub](https://github.com/timsainb/noisereduce). Notice that the saved files are in .wav format, even if the original files are in another format.\n", + "\n", + "### Parameters:\n", + "\n", + "- `audio_source`: path to the audio file or directory containing audio files\n", + "- `target_directory`: path to the directory to save the cleaned audio files.\n", + "- `sample_rate`: Number of samples in one second in the audio file. Pass `None` to keep the original sample rate.\n", + "- `duration`: Duration of the audio file to clean in seconds. Pass `None` to keep the original duration.\n", + "- `channel`: Channel to clean. Pass the number of the channel to clean. To clean all channels, pass `None`.\n", + "- `silence_threshold`: The threshold to remove silence from the audio, in dB. If `None`, no silence removal is performed.\n", + "- `use_multiprocessing`: Number of processes to use for cleaning the audio files. If 0, no multiprocessing is used.\n", + "- `verbose`: Verbosity level. If True, display a progress bar.\n", + "\n", + "#### 2.1. Example" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f10a5ecd-bf90-4650-a42e-d3fbfff78e52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 16:07:39,378 [info] Storing function: {'name': 'noise-reduce-reduce-noise', 'uid': '6e6d6f7c3f8243b995dc1bbcf66f7544', 'db': 'http://mlrun-api:8080'}\n", + "> 2024-03-04 16:07:39,541 [info] Reducing noise from audio files.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Noise-reduction: 0%| | 0/2 [00:00 2024-03-04 16:07:39,565 [info] Reducing noise from test_data.mp3.\n", + "> 2024-03-04 16:07:39,566 [info] Reducing noise from test_data.wav.\n", + "> 2024-03-04 16:07:46,174 [info] Saved cleaned audio file to clean_data/test_data.wav.\n", + "> 2024-03-04 16:07:46,175 [info] Saved cleaned audio file to clean_data/test_data_mp3.wav.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Noise-reduction: 100%|██████████| 2/2 [00:06<00:00, 3.31s/file]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 16:07:46,211 [info] Summarizing the results.\n", + "> 2024-03-04 16:07:46,212 [info] Done (2/2)\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
noise-reduction0Mar 04 16:07:39completednoise-reduce-reduce-noise
v3io_user=yonis
kind=local
owner=yonis
host=jupyter-yoni-d56767c87-678n2
audio_source
target_directory=./clean_data
use_multiprocessing=2
silence_threshold=50
successes
errors
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2024-03-04 16:07:46,389 [info] Run execution finished: {'status': 'completed', 'name': 'noise-reduce-reduce-noise'}\n" + ] + } + ], + "source": [ + "noise_reduction_run = noise_reduction_function.run(\n", + " handler=\"reduce_noise\",\n", + " inputs={\"audio_source\": audio_source},\n", + " params={\n", + " \"target_directory\": \"./clean_data\",\n", + " \"use_multiprocessing\": 2,\n", + " \"silence_threshold\": 50,\n", + " },\n", + " local=True,\n", + " returns=[\"successes: file\", \"errors: file\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "699615d7-bba1-4147-ad3d-d295d794f866", + "metadata": {}, + "source": [ + "### Looking at the result" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "47c4f66a-d5d0-47e5-9842-abbe6653526b", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": { + "test_data.mp3": "clean_data/test_data_mp3.wav", + "test_data.wav": "clean_data/test_data.wav" + }, + "text/plain": [ + "" + ] + }, + "metadata": { + "application/json": { + "expanded": false, + "root": "root" + } + }, + "output_type": "display_data" + }, + { + "data": { + "application/json": {}, + "text/plain": [ + "" + ] + }, + "metadata": { + "application/json": { + "expanded": false, + "root": "root" + } + }, + "output_type": "display_data" + } + ], + "source": [ + "dfn_run.artifact(\"successes\").show()\n", + "dfn_run.artifact(\"errors\").show()" + ] + }, + { + "cell_type": "markdown", + "id": "6eeae1bb-c714-491b-91dd-f22148cd0970", + "metadata": {}, + "source": [ + "The output of this function is the same as the first one." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/noise_reduction/1.1.0/src/noise_reduction.py b/functions/master/noise_reduction/1.1.0/src/noise_reduction.py new file mode 100644 index 00000000..f0fff550 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/noise_reduction.py @@ -0,0 +1,625 @@ +import logging +from abc import ABCMeta, abstractmethod +from multiprocessing import Process, Queue +from pathlib import Path +from typing import List, Tuple, Type, Union + +import librosa +import numpy as np +import torch +from scipy.io import wavfile +from tqdm import tqdm + +#: The value to send into multiprocessing queues to stop the process: +_MULTIPROCESSING_STOP_MARK = "STOP" + +# Get the global logger: +try: + import mlrun + + _LOGGER = mlrun.get_or_create_ctx("noise_reduce").logger +except ModuleNotFoundError: + _LOGGER = logging.getLogger() + + +class ReduceNoiseBase(metaclass=ABCMeta): + """ + Base class for noise reduction. + This class is aimed to be inherited by specific noise reduction algorithms. + You must implement the following methods: + - clean_audio: The method to clean the audio, where the noise reduction algorithm is implemented. + - save_audio: The method to save the audio to a file. + - load_audio: The method to load the audio from a file. + + After implementing the above methods, you can use the reduce_noise method to reduce noise from audio files. + """ + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + ): + self.target_directory = Path(target_directory) + self.verbose = verbose + self.silence_threshold = silence_threshold + + def reduce_noise(self, audio_file: Path) -> Tuple[bool, Tuple[str, str]]: + """ + Reduce noise from the given audio file. + + :param audio_file: The audio file to reduce noise from. + + :returns: A tuple of: + - a boolean indicating whether an error occurred + - a tuple of: + - audio file name + - target path in case of success / error message in case of failure. + """ + try: + if self.verbose: + _LOGGER.info(f"Reducing noise from {audio_file.name}.") + + # Load audio data: + audio = self.load_audio(file=str(audio_file)) + + # Perform noise reduction: + reduced_noise = self.clean_audio(data=audio) + + # Remove silence from the audio if necessary: + reduced_noise = self.remove_silence(audio=reduced_noise) + + # Prepare target path: + target_path = self.update_to_wav_suffix(audio_file=audio_file) + + # Save file: + self.save_audio( + audio=reduced_noise, + target_path=target_path, + ) + + if self.verbose: + _LOGGER.info(f"Saved cleaned audio file to {target_path}.") + + return False, (audio_file.name, str(target_path)) + except Exception as exception: + if self.verbose: + _LOGGER.error(f"Failed to reduce noise from {audio_file.name}.") + _LOGGER.error(f"Error: {exception}") + # Collect the error: + return True, (audio_file.name, str(exception)) + + @abstractmethod + def clean_audio(self, data) -> Union[np.ndarray, torch.Tensor]: + """ + Clean the audio from noise. Here you should implement the noise reduction algorithm. + + :param data: The audio data to clean. + + :returns: The cleaned audio. + """ + pass + + @abstractmethod + def save_audio(self, audio: np.ndarray, target_path: Path): + """ + Save the audio to a file. + + :param audio: The audio to save. + :param target_path: The target path to save the audio to. + """ + pass + + @abstractmethod + def load_audio(self, file: str) -> Tuple[Union[np.ndarray, torch.Tensor], int]: + """ + Load the audio from a file. + + :param file: The file to load the audio from. + + :returns: A tuple of: + - the audio data + - the sample rate + """ + pass + + def update_to_wav_suffix(self, audio_file: Path): + target_path = self.target_directory / audio_file.name + if target_path.suffix != ".wav": + old_suffix = target_path.suffix[1:] + target_path = target_path.with_stem(target_path.stem + f"_{old_suffix}") + return target_path.with_suffix(".wav") + else: + return target_path + + def remove_silence( + self, + audio: np.ndarray, + ): + """ + Remove silence sections from the audio. + + :param audio: The audio to remove silence from. + + :returns: The audio without silence. + """ + if self.silence_threshold is None: + return audio + + # Get the indices of the non-silent frames: + non_silent_indices = librosa.effects.split( + y=audio, + top_db=self.silence_threshold, + frame_length=2048, + hop_length=256, + ) + + # Get the non-silent audio: + non_silent_audio = np.concatenate( + [audio[:, start:end] for start, end in non_silent_indices], axis=1 + ) + + return non_silent_audio + + +class ReduceNoise(ReduceNoiseBase): + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + sample_rate: int = 16000, + duration: int = None, + channel: int = None, + ): + super().__init__(target_directory, verbose, silence_threshold) + self.sample_rate = sample_rate + self.duration = duration + self.channel = channel + + def save_audio(self, audio: np.ndarray, target_path: Path): + # If the audio has more than one channel, transpose it in order to save it: + if len(audio) > 1: + audio = audio.T + + wavfile.write( + filename=target_path, + rate=self.sample_rate, + data=audio, + ) + + def load_audio(self, file: str) -> np.ndarray: + data, sr = librosa.load( + path=file, + sr=self.sample_rate, + mono=False, # keep channels separate + duration=self.duration, + ) + # set sample rate: + self.sample_rate = int(sr) + + # convert to int with scaling for 16-bit integer + data *= 32767 / np.max(np.abs(data)) # re-scaling + data = data.astype(np.int16) # change data type + + # select channel + data_to_reduce = data[self.channel] if self.channel is not None else data + return data_to_reduce + + def clean_audio(self, data: np.ndarray) -> np.ndarray: + try: + import noisereduce + except ImportError as e: + raise ImportError("Please install noisereduce package") from e + + reduced_noise = noisereduce.reduce_noise(y=data, sr=self.sample_rate) + + # add channel back after noise reduction + if self.channel is not None: + # putting the channel back in the data + data[self.channel] = reduced_noise + # updating the data to save + reduced_noise = data + + return reduced_noise + + +class DFN(ReduceNoiseBase): + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + pad: bool = True, + atten_lim_db: int = None, + **kwargs, + ): + super().__init__(target_directory, verbose, silence_threshold) + self.pad = pad + self.atten_lim_db = atten_lim_db + self.kwargs = kwargs + + # import required packages + try: + from df.enhance import init_df + except ImportError as e: + raise ImportError("Please install deepfilternet packages") from e + + if self.verbose: + _LOGGER.info("Loading DeepFilterNet2 model.") + + # Load the model: + model, df_state, _ = init_df() + self.model = model + self.df_state = df_state + self.sample_rate = self.df_state.sr() + + def save_audio(self, audio: np.ndarray, target_path: Path): + try: + from df.enhance import save_audio + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + save_audio( + file=target_path.name, + audio=audio, + sr=self.sample_rate, + output_dir=str(self.target_directory), + ) + + def load_audio(self, file: str) -> torch.Tensor: + try: + from df.enhance import load_audio + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + audio, _ = load_audio(file=file, sr=self.sample_rate, **self.kwargs) + return audio + + def clean_audio(self, data: torch.Tensor) -> torch.Tensor: + try: + from df.enhance import enhance + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + return enhance( + model=self.model, + df_state=self.df_state, + audio=data, + pad=self.pad, + atten_lim_db=self.atten_lim_db, + ) + + +def _multiprocessing_complete_tasks( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + tasks_queue: Queue, + results_queue: Queue, +): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param noise_reduce_type: The noise reduce type to use. + :param noise_reduce_arguments: The noisereduce initialization kwargs. + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + # Initialize the reduce noise object + noise_reducer = noise_reduce_type(**noise_reduce_arguments) + + # Start listening to the tasks queue: + while True: + # Get the audio_file: + audio_file = tasks_queue.get() + if audio_file == _MULTIPROCESSING_STOP_MARK: + break + audio_file = Path(audio_file) + # Apply noise reduction and collect the result: + results_queue.put(noise_reducer.reduce_noise(audio_file=audio_file)) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +def reduce_noise_dfn( + audio_source: str, + target_directory: str, + pad: bool = True, + atten_lim_db: int = None, + silence_threshold: float = None, + use_multiprocessing: int = 0, + verbose: bool = True, + **kwargs, +): + """ + Reduce noise from audio files using DeepFilterNet. + For more information about the noise reduction algorithm see: + https://github.com/Rikorose/DeepFilterNet + Notice that the saved files are in wav format, even if the original files are in other format. + + :param audio_source: path to audio file or directory of audio files + :param target_directory: path to target directory to save cleaned audio files + :param pad: whether to pad the audio file with zeros before cleaning + :param atten_lim_db: maximum attenuation in dB + :param silence_threshold: the threshold to remove silence from the audio, in dB. If None, no silence removal is + performed. + :param use_multiprocessing: Number of processes to use for cleaning the audio files. + If 0, no multiprocessing is used. + :param verbose: verbosity level. If True, display progress bar and logs. + :param kwargs: additional arguments to pass to torchaudio.load(). For more information see: + https://pytorch.org/audio/stable/generated/torchaudio.load.html + """ + if verbose: + _LOGGER.info("Reducing noise from audio files.") + + # create target directory: + target_directory = _create_target_directory(target_directory) + + # get audio files: + audio_files = _get_audio_files(audio_source) + + noise_reduce_arguments = { + "target_directory": target_directory, + "pad": pad, + "atten_lim_db": atten_lim_db, + "silence_threshold": silence_threshold, + **kwargs, + } + + if use_multiprocessing: + results = _parallel_run( + noise_reduce_type=DFN, + noise_reduce_arguments=noise_reduce_arguments, + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + else: + results = _run( + noise_reduce_type=DFN, + noise_reduce_arguments=noise_reduce_arguments, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + + return _process_results(results, verbose) + + +def reduce_noise( + audio_source: str, + target_directory: str, + sample_rate: int = 16000, + duration: int = None, + channel: int = None, + silence_threshold: float = None, + use_multiprocessing: int = 0, + verbose: bool = True, +): + """ + Reduce noise from audio file or directory containing audio files. + The audio files must be in .wav format. + The cleaned audio files will be saved in the target_directory. + For information about the noise reduction algorithm see: + https://github.com/timsainb/noisereduce + Notice that the saved files are in wav format, even if the original files are in other format. + + :param audio_source: path to audio file or directory containing audio files + :param target_directory: path to directory to save the cleaned audio files. + :param sample_rate: Number of samples in one second in the audio file. + Pass `None` to keep the original sample rate. + :param duration: Duration of the audio file to clean in seconds. + Pass `None` to keep the original duration. + :param channel: Channel to clean. Pass the number of the channel to clean. + To clean all channels pass None. + :param silence_threshold: The threshold to remove silence from the audio, in dB. + If None, no silence removal is performed. + :param use_multiprocessing: Number of processes to use for cleaning the audio files. + If 0, no multiprocessing is used. + :param verbose: Verbosity level. If True, display progress bar. + """ + if verbose: + _LOGGER.info("Reducing noise from audio files.") + + # create target directory: + target_directory = _create_target_directory(target_directory) + + # get audio files: + audio_files = _get_audio_files(audio_source) + + # Create the reduce noise object: + noise_reduce_arguments = { + "target_directory": target_directory, + "sample_rate": sample_rate, + "duration": duration, + "channel": channel, + "silence_threshold": silence_threshold, + } + + if use_multiprocessing: + results = _parallel_run( + noise_reduce_type=ReduceNoise, + noise_reduce_arguments=noise_reduce_arguments, + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + else: + results = _run( + noise_reduce_type=ReduceNoise, + noise_reduce_arguments=noise_reduce_arguments, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + + return _process_results(results, verbose) + + +def _create_target_directory(target_directory: str) -> str: + target_directory = Path(target_directory) + if not target_directory.exists(): + target_directory.mkdir(parents=True, exist_ok=True) + return str(target_directory) + + +def _get_audio_files(audio_source: str): + audio_source = Path(audio_source) + audio_files = [] + if audio_source.is_dir(): + audio_files = list(audio_source.glob("*.*")) + elif audio_source.is_file(): + audio_files.append(audio_source) + else: + raise ValueError( + f"audio_source must be a file or a directory, got {audio_source}" + ) + return audio_files + + +def _parallel_run( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + n_workers: int, + audio_files: List[Path], + description: str, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run multiple noise reduce workers with multiprocessing to complete the tasks that will be created on the provided + files using the given task creator. + + :param noise_reduce_type: The noise reduce type to use. + :param n_workers: The number of workers to use. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Check the number of workers: + if n_workers > len(audio_files): + _LOGGER.warning( + f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). " + f"Setting the number of workers to {len(audio_files)}." + ) + n_workers = len(audio_files) + + # Initialize the multiprocessing queues: + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={ + "noise_reduce_type": noise_reduce_type, + "noise_reduce_arguments": noise_reduce_arguments, + "tasks_queue": tasks_queue, + "results_queue": results_queue, + }, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + for p in task_completion_processes: + p.start() + + # Put the tasks in the queue: + for audio_file in audio_files: + # tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple()) + tasks_queue.put(audio_file) + + # Put the stop marks in the queue: + for _ in range(n_workers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + # Collect the results: + results = [] + stop_marks_counter = 0 + with tqdm( + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ) as progressbar: + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, str]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + progressbar.update(1) + + # Wait for the processes to finish: + for p in task_completion_processes: + p.join() + + return results + + +def _run( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + audio_files: List[Path], + description: str, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run the noise reduce algorithm on the given audio files and collect the results. + + :param noise_reduce_type: The noise reduce type to use. + :param noise_reduce_arguments: The noisereduce initialization kwargs. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Create the reduce noise object: + noise_reducer = noise_reduce_type(**noise_reduce_arguments) + + # Run the noise reduce algorithm on the audio files and collect the results: + results = [] + for audio_file in tqdm( + audio_files, + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ): + results.append(noise_reducer.reduce_noise(audio_file=audio_file)) + + return results + + +def _process_results( + results: List[Tuple[bool, Tuple[str, str]]], verbose: bool +) -> Tuple[dict, dict]: + """ + Process the results of the tasks. + + :param results: The results to process. + :param verbose: Verbosity. + + :returns: The processed results as a tuple of successes and errors. + """ + if verbose: + _LOGGER.info("Summarizing the results.") + successes = {} + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes[result[0]] = result[1] + if verbose: + _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n") + + return successes, errors diff --git a/functions/master/noise_reduction/1.1.0/src/requirements.txt b/functions/master/noise_reduction/1.1.0/src/requirements.txt new file mode 100644 index 00000000..30934ad7 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/requirements.txt @@ -0,0 +1,5 @@ +tqdm +deepfilternet +librosa +noisereduce +torchaudio>=2.1.2 \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/src/test_noise_reduction.py b/functions/master/noise_reduction/1.1.0/src/test_noise_reduction.py new file mode 100644 index 00000000..a7737756 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/src/test_noise_reduction.py @@ -0,0 +1,75 @@ +import tempfile + +import mlrun +import pytest + + +@pytest.mark.parametrize( + "audio_source", + [ + "data/test_data.wav", + "data/test_data.mp3", + "data", + ], +) +def test_reduce_noise(audio_source): + # set up the project and function + artifact_path = tempfile.TemporaryDirectory().name + project = mlrun.new_project("noise-reduction") + noise_reduction_function = project.set_function( + func="function.yaml", + name="reduce_noise", + kind="job", + image="mlrun/mlrun", + ) + + # run the function + noise_reduction_run = noise_reduction_function.run( + handler="reduce_noise", + inputs={"audio_source": audio_source}, + params={ + "target_directory": artifact_path + "/data", + "sample_rate": None, + }, + local=True, + artifact_path=artifact_path, + returns=["successes: file", "errors: file"], + ) + + assert noise_reduction_run.outputs["successes"] + + +@pytest.mark.parametrize( + "audio_source", + [ + "data/test_data.wav", + "data/test_data.mp3", + "data", + ], +) +def test_reduce_noise_dfn(audio_source): + # set up the project and function + artifact_path = tempfile.TemporaryDirectory().name + project = mlrun.new_project("noise-reduction") + noise_reduction_function = project.set_function( + func="function.yaml", + name="reduce_noise", + kind="job", + image="mlrun/mlrun", + ) + + # run the function + noise_reduction_run = noise_reduction_function.run( + handler="reduce_noise_dfn", + inputs={"audio_source": audio_source}, + params={ + "target_directory": artifact_path + "/data", + "atten_lim_db": 50, + }, + local=True, + artifact_path=artifact_path, + returns=["successes: file", "errors: file"], + ) + + # assert that the function run completed successfully + assert noise_reduction_run.outputs["successes"] diff --git a/functions/master/noise_reduction/1.1.0/static/documentation.html b/functions/master/noise_reduction/1.1.0/static/documentation.html new file mode 100644 index 00000000..a772e518 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/documentation.html @@ -0,0 +1,518 @@ + + + + + + + +noise_reduction package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

noise_reduction package#

+
+

Submodules#

+
+
+

noise_reduction.noise_reduction module#

+
+
+class noise_reduction.noise_reduction.DFN(target_directory: Path, verbose: bool = True, silence_threshold: float | None = None, pad: bool = True, atten_lim_db: int | None = None, **kwargs)[source]#
+

Bases: ReduceNoiseBase

+
+
+clean_audio(data: torch.Tensor) torch.Tensor[source]#
+

Clean the audio from noise. Here you should implement the noise reduction algorithm.

+
+
Parameters:
+

data – The audio data to clean.

+
+
Returns:
+

The cleaned audio.

+
+
+
+
+
+load_audio(file: str) torch.Tensor[source]#
+

Load the audio from a file.

+
+
Parameters:
+

file – The file to load the audio from.

+
+
Returns:
+

A tuple of: +- the audio data +- the sample rate

+
+
+
+
+
+save_audio(audio: ndarray, target_path: Path)[source]#
+

Save the audio to a file.

+
+
Parameters:
+
    +
  • audio – The audio to save.

  • +
  • target_path – The target path to save the audio to.

  • +
+
+
+
+
+
+
+class noise_reduction.noise_reduction.ReduceNoise(target_directory: Path, verbose: bool = True, silence_threshold: float | None = None, sample_rate: int = 16000, duration: int | None = None, channel: int | None = None)[source]#
+

Bases: ReduceNoiseBase

+
+
+clean_audio(data: ndarray) ndarray[source]#
+

Clean the audio from noise. Here you should implement the noise reduction algorithm.

+
+
Parameters:
+

data – The audio data to clean.

+
+
Returns:
+

The cleaned audio.

+
+
+
+
+
+load_audio(file: str) ndarray[source]#
+

Load the audio from a file.

+
+
Parameters:
+

file – The file to load the audio from.

+
+
Returns:
+

A tuple of: +- the audio data +- the sample rate

+
+
+
+
+
+save_audio(audio: ndarray, target_path: Path)[source]#
+

Save the audio to a file.

+
+
Parameters:
+
    +
  • audio – The audio to save.

  • +
  • target_path – The target path to save the audio to.

  • +
+
+
+
+
+
+
+class noise_reduction.noise_reduction.ReduceNoiseBase(target_directory: Path, verbose: bool = True, silence_threshold: float | None = None)[source]#
+

Bases: object

+

Base class for noise reduction. +This class is aimed to be inherited by specific noise reduction algorithms. +You must implement the following methods: +- clean_audio: The method to clean the audio, where the noise reduction algorithm is implemented. +- save_audio: The method to save the audio to a file. +- load_audio: The method to load the audio from a file.

+

After implementing the above methods, you can use the reduce_noise method to reduce noise from audio files.

+
+
+abstract clean_audio(data) ndarray | torch.Tensor[source]#
+

Clean the audio from noise. Here you should implement the noise reduction algorithm.

+
+
Parameters:
+

data – The audio data to clean.

+
+
Returns:
+

The cleaned audio.

+
+
+
+
+
+abstract load_audio(file: str) Tuple[ndarray | torch.Tensor, int][source]#
+

Load the audio from a file.

+
+
Parameters:
+

file – The file to load the audio from.

+
+
Returns:
+

A tuple of: +- the audio data +- the sample rate

+
+
+
+
+
+reduce_noise(audio_file: Path) Tuple[bool, Tuple[str, str]][source]#
+

Reduce noise from the given audio file.

+
+
Parameters:
+

audio_file – The audio file to reduce noise from.

+
+
Returns:
+

A tuple of: +- a boolean indicating whether an error occurred +- a tuple of:

+
+
    +
  • audio file name

  • +
  • target path in case of success / error message in case of failure.

  • +
+
+

+
+
+
+
+
+remove_silence(audio: ndarray)[source]#
+

Remove silence sections from the audio.

+
+
Parameters:
+

audio – The audio to remove silence from.

+
+
Returns:
+

The audio without silence.

+
+
+
+
+
+abstract save_audio(audio: ndarray, target_path: Path)[source]#
+

Save the audio to a file.

+
+
Parameters:
+
    +
  • audio – The audio to save.

  • +
  • target_path – The target path to save the audio to.

  • +
+
+
+
+
+
+update_to_wav_suffix(audio_file: Path)[source]#
+
+
+
+
+noise_reduction.noise_reduction.reduce_noise(audio_source: str, target_directory: str, sample_rate: int = 16000, duration: int | None = None, channel: int | None = None, silence_threshold: float | None = None, use_multiprocessing: int = 0, verbose: bool = True)[source]#
+

Reduce noise from audio file or directory containing audio files. +The audio files must be in .wav format. +The cleaned audio files will be saved in the target_directory. +For information about the noise reduction algorithm see: +timsainb/noisereduce +Notice that the saved files are in wav format, even if the original files are in other format.

+
+
Parameters:
+
    +
  • audio_source – path to audio file or directory containing audio files

  • +
  • target_directory – path to directory to save the cleaned audio files.

  • +
  • sample_rate – Number of samples in one second in the audio file. +Pass None to keep the original sample rate.

  • +
  • duration – Duration of the audio file to clean in seconds. +Pass None to keep the original duration.

  • +
  • channel – Channel to clean. Pass the number of the channel to clean. +To clean all channels pass None.

  • +
  • silence_threshold – The threshold to remove silence from the audio, in dB. +If None, no silence removal is performed.

  • +
  • use_multiprocessing – Number of processes to use for cleaning the audio files. +If 0, no multiprocessing is used.

  • +
  • verbose – Verbosity level. If True, display progress bar.

  • +
+
+
+
+
+
+noise_reduction.noise_reduction.reduce_noise_dfn(audio_source: str, target_directory: str, pad: bool = True, atten_lim_db: int | None = None, silence_threshold: float | None = None, use_multiprocessing: int = 0, verbose: bool = True, **kwargs)[source]#
+

Reduce noise from audio files using DeepFilterNet. +For more information about the noise reduction algorithm see: +Rikorose/DeepFilterNet +Notice that the saved files are in wav format, even if the original files are in other format.

+
+
Parameters:
+
    +
  • audio_source – path to audio file or directory of audio files

  • +
  • target_directory – path to target directory to save cleaned audio files

  • +
  • pad – whether to pad the audio file with zeros before cleaning

  • +
  • atten_lim_db – maximum attenuation in dB

  • +
  • silence_threshold – the threshold to remove silence from the audio, in dB. If None, no silence removal is +performed.

  • +
  • use_multiprocessing – Number of processes to use for cleaning the audio files. +If 0, no multiprocessing is used.

  • +
  • verbose – verbosity level. If True, display progress bar and logs.

  • +
  • kwargs – additional arguments to pass to torchaudio.load(). For more information see: +https://pytorch.org/audio/stable/generated/torchaudio.load.html

  • +
+
+
+
+
+
+

Module contents#

+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/static/example.html b/functions/master/noise_reduction/1.1.0/static/example.html new file mode 100644 index 00000000..7ad47bf9 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/example.html @@ -0,0 +1,906 @@ + + + + + + + +Noise Reduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+

Noise Reduction#

+
+

Table of Contents#

+
    +
  1. Introduction

  2. +
  3. Project Setup

  4. +
  5. Noise Reduction Techniques

    +
      +
    1. DeepFilterNet

    2. +
    3. Spectral Gating

    4. +
    +
  6. +
+
+
+

Introduction#

+

Noise reduction is a crucial signal processing technique used to enhance the quality of signals by minimizing unwanted or irrelevant noise. This technique finds applications in various fields such as audio processing, image processing, telecommunications, and more. The goal is to extract the useful information from a signal while suppressing undesirable background noise.

+
+
+
import mlrun
+
+
+
+
+
+
+

Setting up a project#

+

First of all we need to create a project with the noise-reduction function

+
+
+
# Creating a project
+project = mlrun.get_or_create_project("noise-reduction")
+# Importing the function from hub
+noise_reduction_function = project.set_function("hub://noise_reduction")
+
+
+
+
+
> 2024-03-04 15:54:53,561 [info] Project loaded successfully: {'project_name': 'noise-reduction'}
+
+
+
+
+
+
+
# Audio source can be either a single file or a directory of audio files
+audio_source = "data"
+
+
+
+
+
+
+

Noise Reduction Techniques#

+

+
+

1. DeepFilterNet#

+

image.png

+

In order to use this technique, you simply need to use the reduce_noise_dfn handler.

+

Reduce noise from audio files using DeepFilterNet. For more information about the noise reduction algorithm, see DeepFilterNet GitHub. Notice that the saved files are in wav format, even if the original files are in other formats.

+
+
+

Parameters:#

+
    +
  • audio_source: path to the audio file or directory of audio files

  • +
  • target_directory: path to the target directory to save cleaned audio files

  • +
  • pad: whether to pad the audio file with zeros before cleaning

  • +
  • atten_lim_db: maximum attenuation in dB

  • +
  • silence_threshold: the threshold to remove silence from the audio, in dB. If None, no silence removal is performed.

  • +
  • use_multiprocessing: Number of processes to use for cleaning the audio files. If 0, no multiprocessing is used.

  • +
  • verbose: verbosity level. If True, display progress bar and logs.

  • +
  • kwargs: additional arguments to pass to torchaudio.load(). For more information, see torchaudio.load().

  • +
+

In the examples below, the function is running locally, for running remotely, it is required to build the function’s image first (need to execute only once):

+
noise_reduction_function.apply(mlrun.auto_mount()) # required for local files
+project.build_function("noise-reduction")
+
+
+
+

1.1. Example#

+
+
+
dfn_run = noise_reduction_function.run(
+    handler="reduce_noise_dfn",
+    inputs={"audio_source": audio_source},
+    params={
+        "target_directory": "./clean_data",
+        "use_multiprocessing": 2,
+        "silence_threshold": 50,
+        "atten_lim_db": 10,
+    },
+    returns=["successes: file", "errors: file"],
+    local=True,
+)
+
+
+
+
+
> 2024-03-04 15:54:56,999 [info] Storing function: {'name': 'noise-reduce-reduce-noise-dfn', 'uid': '9732dac831784a6a8b53acab5ff83a08', 'db': 'http://mlrun-api:8080'}
+> 2024-03-04 15:55:07,525 [info] logging run results to: http://mlrun-api:8080
+> 2024-03-04 15:55:07,702 [info] Reducing noise from audio files.
+
+
+
Noise-reduction:   0%|          | 0/2 [00:00<?, ?file/s]`torchaudio.backend.common.AudioMetaData` has been moved to `torchaudio.AudioMetaData`. Please update the import path.
+
+
+
> 2024-03-04 15:55:08,437 [info] Loading DeepFilterNet2 model.
+
+
+
`torchaudio.backend.common.AudioMetaData` has been moved to `torchaudio.AudioMetaData`. Please update the import path.
+
+
+
2024-03-04 15:55:08 | INFO     | DF | Running on torch 2.1.2+cu121
+2024-03-04 15:55:08 | INFO     | DF | Running on host jupyter-yoni-d56767c87-678n2
+> 2024-03-04 15:55:08,464 [info] Loading DeepFilterNet2 model.
+2024-03-04 15:55:08 | INFO     | DF | Running on torch 2.1.2+cu121
+2024-03-04 15:55:08 | INFO     | DF | Running on host jupyter-yoni-d56767c87-678n2
+2024-03-04 15:55:08 | INFO     | DF | Loading model settings of DeepFilterNet3
+2024-03-04 15:55:08 | INFO     | DF | Using DeepFilterNet3 model at /igz/.cache/DeepFilterNet/DeepFilterNet3
+2024-03-04 15:55:08 | INFO     | DF | Initializing model `deepfilternet3`
+2024-03-04 15:55:08 | INFO     | DF | Loading model settings of DeepFilterNet3
+2024-03-04 15:55:08 | INFO     | DF | Using DeepFilterNet3 model at /igz/.cache/DeepFilterNet/DeepFilterNet3
+2024-03-04 15:55:08 | INFO     | DF | Initializing model `deepfilternet3`
+2024-03-04 15:55:08 | INFO     | DF | Found checkpoint /igz/.cache/DeepFilterNet/DeepFilterNet3/checkpoints/model_120.ckpt.best with epoch 120
+2024-03-04 15:55:08 | INFO     | DF | Found checkpoint /igz/.cache/DeepFilterNet/DeepFilterNet3/checkpoints/model_120.ckpt.best with epoch 120
+2024-03-04 15:55:08 | INFO     | DF | Running on device cpu
+2024-03-04 15:55:08 | INFO     | DF | Running on device cpu
+2024-03-04 15:55:08 | INFO     | DF | Model loaded
+2024-03-04 15:55:08 | INFO     | DF | Model loaded
+> 2024-03-04 15:55:08,635 [info] Reducing noise from test_data.mp3.
+> 2024-03-04 15:55:08,636 [info] Reducing noise from test_data.wav.
+
+
+
2024-03-04 15:55:08 | WARNING  | DF | Audio sampling rate does not match model sampling rate (16000, 48000). Resampling...
+"sinc_interpolation" resampling method name is being deprecated and replaced by "sinc_interp_hann" in the next release. The default behavior remains unchanged.
+The MPEG_LAYER_III subtype is unknown to TorchAudio. As a result, the bits_per_sample attribute will be set to 0. If you are seeing this warning, please report by opening an issue on github (after checking for existing/closed ones). You may otherwise ignore this warning.
+2024-03-04 15:55:08 | WARNING  | DF | Audio sampling rate does not match model sampling rate (16000, 48000). Resampling...
+"sinc_interpolation" resampling method name is being deprecated and replaced by "sinc_interp_hann" in the next release. The default behavior remains unchanged.
+
+
+
> 2024-03-04 15:55:16,701 [info] Saved cleaned audio file to clean_data/test_data.wav.
+> 2024-03-04 15:55:16,706 [info] Saved cleaned audio file to clean_data/test_data_mp3.wav.
+
+
+
Noise-reduction: 100%|██████████| 2/2 [00:09<00:00,  4.51s/file]
+
+
+
> 2024-03-04 15:55:16,791 [info] Summarizing the results.
+> 2024-03-04 15:55:16,792 [info] Done (2/2)
+
+
+

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
noise-reduction0Mar 04 15:54:57completednoise-reduce-reduce-noise-dfn
v3io_user=yonis
kind=local
owner=yonis
host=jupyter-yoni-d56767c87-678n2
audio_source
target_directory=./clean_data
use_multiprocessing=2
silence_threshold=50
atten_lim_db=10
successes
errors
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2024-03-04 15:55:17,976 [info] Run execution finished: {'status': 'completed', 'name': 'noise-reduce-reduce-noise-dfn'}
+
+
+
+
+
+
+
+

Looking at the result#

+
+
+
dfn_run.artifact("successes").show()
+dfn_run.artifact("errors").show()
+
+
+
+
+
<IPython.core.display.JSON object>
+
+
+
<IPython.core.display.JSON object>
+
+
+
+
+

+
+
+

2. Spectral Gating#

+

image.png

+

In order to use this technique, you simply need to use the reduce_noise handler.

+

Spectral gating selectively filters signal frequencies based on amplitude, offering targeted noise reduction or feature enhancement in signal processing applications.

+

Reduce noise from an audio file or directory containing audio files. The audio files must be in .wav format. The cleaned audio files will be saved in the target directory. For information about the noise reduction algorithm, see noisereduce GitHub. Notice that the saved files are in .wav format, even if the original files are in another format.

+
+
+

Parameters:#

+
    +
  • audio_source: path to the audio file or directory containing audio files

  • +
  • target_directory: path to the directory to save the cleaned audio files.

  • +
  • sample_rate: Number of samples in one second in the audio file. Pass None to keep the original sample rate.

  • +
  • duration: Duration of the audio file to clean in seconds. Pass None to keep the original duration.

  • +
  • channel: Channel to clean. Pass the number of the channel to clean. To clean all channels, pass None.

  • +
  • silence_threshold: The threshold to remove silence from the audio, in dB. If None, no silence removal is performed.

  • +
  • use_multiprocessing: Number of processes to use for cleaning the audio files. If 0, no multiprocessing is used.

  • +
  • verbose: Verbosity level. If True, display a progress bar.

  • +
+
+

2.1. Example#

+
+
+
noise_reduction_run = noise_reduction_function.run(
+    handler="reduce_noise",
+    inputs={"audio_source": audio_source},
+    params={
+        "target_directory": "./clean_data",
+        "use_multiprocessing": 2,
+        "silence_threshold": 50,
+    },
+    local=True,
+    returns=["successes: file", "errors: file"],
+)
+
+
+
+
+
> 2024-03-04 16:07:39,378 [info] Storing function: {'name': 'noise-reduce-reduce-noise', 'uid': '6e6d6f7c3f8243b995dc1bbcf66f7544', 'db': 'http://mlrun-api:8080'}
+> 2024-03-04 16:07:39,541 [info] Reducing noise from audio files.
+
+
+
Noise-reduction:   0%|          | 0/2 [00:00<?, ?file/s]
+
+
+
> 2024-03-04 16:07:39,565 [info] Reducing noise from test_data.mp3.
+> 2024-03-04 16:07:39,566 [info] Reducing noise from test_data.wav.
+> 2024-03-04 16:07:46,174 [info] Saved cleaned audio file to clean_data/test_data.wav.
+> 2024-03-04 16:07:46,175 [info] Saved cleaned audio file to clean_data/test_data_mp3.wav.
+
+
+
Noise-reduction: 100%|██████████| 2/2 [00:06<00:00,  3.31s/file]
+
+
+
> 2024-03-04 16:07:46,211 [info] Summarizing the results.
+> 2024-03-04 16:07:46,212 [info] Done (2/2)
+
+
+

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
noise-reduction0Mar 04 16:07:39completednoise-reduce-reduce-noise
v3io_user=yonis
kind=local
owner=yonis
host=jupyter-yoni-d56767c87-678n2
audio_source
target_directory=./clean_data
use_multiprocessing=2
silence_threshold=50
successes
errors
+
+ +
+

+
+
+
> to track results use the .show() or .logs() methods or click here to open in UI
> 2024-03-04 16:07:46,389 [info] Run execution finished: {'status': 'completed', 'name': 'noise-reduce-reduce-noise'}
+
+
+
+
+
+
+
+

Looking at the result#

+
+
+
dfn_run.artifact("successes").show()
+dfn_run.artifact("errors").show()
+
+
+
+
+
<IPython.core.display.JSON object>
+
+
+
<IPython.core.display.JSON object>
+
+
+
+
+

The output of this function is the same as the first one.

+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/static/function.html b/functions/master/noise_reduction/1.1.0/static/function.html new file mode 100644 index 00000000..8c061036 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/function.html @@ -0,0 +1,214 @@ + + + + + + + + + + + Source + + + + +
+        
+spec:
+  entry_points:
+    reduce_noise:
+      has_kwargs: false
+      name: reduce_noise
+      has_varargs: false
+      doc: 'Reduce noise from audio file or directory containing audio files.
+
+        The audio files must be in .wav format.
+
+        The cleaned audio files will be saved in the target_directory.
+
+        For information about the noise reduction algorithm see:
+
+        https://github.com/timsainb/noisereduce
+
+        Notice that the saved files are in wav format, even if the original files
+        are in other format.'
+      parameters:
+      - name: audio_source
+        type: str
+        doc: path to audio file or directory containing audio files
+      - name: target_directory
+        type: str
+        doc: path to directory to save the cleaned audio files.
+      - name: sample_rate
+        type: int
+        doc: Number of samples in one second in the audio file. Pass `None` to keep
+          the original sample rate.
+        default: 16000
+      - name: duration
+        type: int
+        doc: Duration of the audio file to clean in seconds. Pass `None` to keep the
+          original duration.
+        default: null
+      - name: channel
+        type: int
+        doc: Channel to clean. Pass the number of the channel to clean. To clean all
+          channels pass None.
+        default: null
+      - name: silence_threshold
+        type: float
+        doc: The threshold to remove silence from the audio, in dB. If None, no silence
+          removal is performed.
+        default: null
+      - name: use_multiprocessing
+        type: int
+        doc: Number of processes to use for cleaning the audio files. If 0, no multiprocessing
+          is used.
+        default: 0
+      - name: verbose
+        type: bool
+        doc: Verbosity level. If True, display progress bar.
+        default: true
+      lineno: 388
+    clean_audio:
+      has_kwargs: false
+      name: clean_audio
+      has_varargs: false
+      outputs:
+      - type: torch.Tensor
+      doc: ''
+      parameters:
+      - name: self
+      - name: data
+        type: Tensor
+      lineno: 276
+    save_audio:
+      has_kwargs: false
+      name: save_audio
+      has_varargs: false
+      doc: ''
+      parameters:
+      - name: self
+      - name: audio
+        type: ndarray
+      - name: target_path
+        type: Path
+      lineno: 256
+    load_audio:
+      has_kwargs: false
+      name: load_audio
+      has_varargs: false
+      outputs:
+      - type: torch.Tensor
+      doc: ''
+      parameters:
+      - name: self
+      - name: file
+        type: str
+      lineno: 268
+    update_to_wav_suffix:
+      has_kwargs: false
+      name: update_to_wav_suffix
+      has_varargs: false
+      doc: ''
+      parameters:
+      - name: self
+      - name: audio_file
+        type: Path
+      lineno: 125
+    remove_silence:
+      has_kwargs: false
+      name: remove_silence
+      has_varargs: false
+      outputs:
+      - doc: The audio without silence.
+      doc: Remove silence sections from the audio.
+      parameters:
+      - name: self
+      - name: audio
+        type: ndarray
+        doc: The audio to remove silence from.
+      lineno: 134
+    reduce_noise_dfn:
+      has_kwargs: true
+      name: reduce_noise_dfn
+      has_varargs: false
+      doc: 'Reduce noise from audio files using DeepFilterNet.
+
+        For more information about the noise reduction algorithm see:
+
+        https://github.com/Rikorose/DeepFilterNet
+
+        Notice that the saved files are in wav format, even if the original files
+        are in other format.'
+      parameters:
+      - name: audio_source
+        type: str
+        doc: path to audio file or directory of audio files
+      - name: target_directory
+        type: str
+        doc: path to target directory to save cleaned audio files
+      - name: pad
+        type: bool
+        doc: whether to pad the audio file with zeros before cleaning
+        default: true
+      - name: atten_lim_db
+        type: int
+        doc: maximum attenuation in dB
+        default: null
+      - name: silence_threshold
+        type: float
+        doc: the threshold to remove silence from the audio, in dB. If None, no silence
+          removal is performed.
+        default: null
+      - name: use_multiprocessing
+        type: int
+        doc: Number of processes to use for cleaning the audio files. If 0, no multiprocessing
+          is used.
+        default: 0
+      - name: verbose
+        type: bool
+        doc: verbosity level. If True, display progress bar and logs.
+        default: true
+      lineno: 322
+  build:
+    code_origin: ''
+    base_image: mlrun/mlrun
+    requirements:
+    - librosa
+    - noisereduce
+    - deepfilternet
+    - torchaudio>=2.1.2
+    functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
+    origin_filename: ''
+  description: Reduce noise from audio files
+  command: ''
+  image: ''
+  default_handler: reduce_noise
+  disable_auto_mount: false
+metadata:
+  name: noise-reduction
+  tag: ''
+  categories:
+  - data-preparation
+  - audio
+kind: job
+verbose: false
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/static/item.html b/functions/master/noise_reduction/1.1.0/static/item.html new file mode 100644 index 00000000..63a2e019 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/item.html @@ -0,0 +1,63 @@ + + + + + + + + + + + Source + + + + +
+        
+apiVersion: v1
+categories:
+  - data-preparation
+  - audio
+description: Reduce noise from audio files
+doc: ''
+example: noise_reduction.ipynb
+generationDate: 2024-03-04:17-30
+hidden: false
+icon: ''
+labels:
+  author: yonatans
+maintainers: []
+mlrunVersion: 1.7.0
+name: noise-reduction
+platformVersion: 3.5.3
+spec:
+  filename: noise_reduction.py
+  handler: reduce_noise
+  image: mlrun/mlrun
+  kind: job
+  requirements: [
+    librosa,
+    noisereduce,
+    deepfilternet,
+    torchaudio>=2.1.2,
+  ]
+url: ''
+version: 1.1.0
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/static/noise_reduction.html b/functions/master/noise_reduction/1.1.0/static/noise_reduction.html new file mode 100644 index 00000000..cfbc45de --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/noise_reduction.html @@ -0,0 +1,848 @@ + + + + + + + +noise_reduction.noise_reduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+

+ +
+
+
+
+
+ +
+

Source code for noise_reduction.noise_reduction

+import logging
+from abc import ABCMeta, abstractmethod
+from multiprocessing import Process, Queue
+from pathlib import Path
+from typing import List, Tuple, Type, Union
+
+import librosa
+import numpy as np
+import torch
+from scipy.io import wavfile
+from tqdm import tqdm
+
+#: The value to send into multiprocessing queues to stop the process:
+_MULTIPROCESSING_STOP_MARK = "STOP"
+
+# Get the global logger:
+try:
+    import mlrun
+
+    _LOGGER = mlrun.get_or_create_ctx("noise_reduce").logger
+except ModuleNotFoundError:
+    _LOGGER = logging.getLogger()
+
+
+
+[docs] +class ReduceNoiseBase(metaclass=ABCMeta): + """ + Base class for noise reduction. + This class is aimed to be inherited by specific noise reduction algorithms. + You must implement the following methods: + - clean_audio: The method to clean the audio, where the noise reduction algorithm is implemented. + - save_audio: The method to save the audio to a file. + - load_audio: The method to load the audio from a file. + + After implementing the above methods, you can use the reduce_noise method to reduce noise from audio files. + """ + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + ): + self.target_directory = Path(target_directory) + self.verbose = verbose + self.silence_threshold = silence_threshold + +
+[docs] + def reduce_noise(self, audio_file: Path) -> Tuple[bool, Tuple[str, str]]: + """ + Reduce noise from the given audio file. + + :param audio_file: The audio file to reduce noise from. + + :returns: A tuple of: + - a boolean indicating whether an error occurred + - a tuple of: + - audio file name + - target path in case of success / error message in case of failure. + """ + try: + if self.verbose: + _LOGGER.info(f"Reducing noise from {audio_file.name}.") + + # Load audio data: + audio = self.load_audio(file=str(audio_file)) + + # Perform noise reduction: + reduced_noise = self.clean_audio(data=audio) + + # Remove silence from the audio if necessary: + reduced_noise = self.remove_silence(audio=reduced_noise) + + # Prepare target path: + target_path = self.update_to_wav_suffix(audio_file=audio_file) + + # Save file: + self.save_audio( + audio=reduced_noise, + target_path=target_path, + ) + + if self.verbose: + _LOGGER.info(f"Saved cleaned audio file to {target_path}.") + + return False, (audio_file.name, str(target_path)) + except Exception as exception: + if self.verbose: + _LOGGER.error(f"Failed to reduce noise from {audio_file.name}.") + _LOGGER.error(f"Error: {exception}") + # Collect the error: + return True, (audio_file.name, str(exception))
+ + +
+[docs] + @abstractmethod + def clean_audio(self, data) -> Union[np.ndarray, torch.Tensor]: + """ + Clean the audio from noise. Here you should implement the noise reduction algorithm. + + :param data: The audio data to clean. + + :returns: The cleaned audio. + """ + pass
+ + +
+[docs] + @abstractmethod + def save_audio(self, audio: np.ndarray, target_path: Path): + """ + Save the audio to a file. + + :param audio: The audio to save. + :param target_path: The target path to save the audio to. + """ + pass
+ + +
+[docs] + @abstractmethod + def load_audio(self, file: str) -> Tuple[Union[np.ndarray, torch.Tensor], int]: + """ + Load the audio from a file. + + :param file: The file to load the audio from. + + :returns: A tuple of: + - the audio data + - the sample rate + """ + pass
+ + +
+[docs] + def update_to_wav_suffix(self, audio_file: Path): + target_path = self.target_directory / audio_file.name + if target_path.suffix != ".wav": + old_suffix = target_path.suffix[1:] + target_path = target_path.with_stem(target_path.stem + f"_{old_suffix}") + return target_path.with_suffix(".wav") + else: + return target_path
+ + +
+[docs] + def remove_silence( + self, + audio: np.ndarray, + ): + """ + Remove silence sections from the audio. + + :param audio: The audio to remove silence from. + + :returns: The audio without silence. + """ + if self.silence_threshold is None: + return audio + + # Get the indices of the non-silent frames: + non_silent_indices = librosa.effects.split( + y=audio, + top_db=self.silence_threshold, + frame_length=2048, + hop_length=256, + ) + + # Get the non-silent audio: + non_silent_audio = np.concatenate( + [audio[:, start:end] for start, end in non_silent_indices], axis=1 + ) + + return non_silent_audio
+
+ + + +
+[docs] +class ReduceNoise(ReduceNoiseBase): + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + sample_rate: int = 16000, + duration: int = None, + channel: int = None, + ): + super().__init__(target_directory, verbose, silence_threshold) + self.sample_rate = sample_rate + self.duration = duration + self.channel = channel + +
+[docs] + def save_audio(self, audio: np.ndarray, target_path: Path): + # If the audio has more than one channel, transpose it in order to save it: + if len(audio) > 1: + audio = audio.T + + wavfile.write( + filename=target_path, + rate=self.sample_rate, + data=audio, + )
+ + +
+[docs] + def load_audio(self, file: str) -> np.ndarray: + data, sr = librosa.load( + path=file, + sr=self.sample_rate, + mono=False, # keep channels separate + duration=self.duration, + ) + # set sample rate: + self.sample_rate = int(sr) + + # convert to int with scaling for 16-bit integer + data *= 32767 / np.max(np.abs(data)) # re-scaling + data = data.astype(np.int16) # change data type + + # select channel + data_to_reduce = data[self.channel] if self.channel is not None else data + return data_to_reduce
+ + +
+[docs] + def clean_audio(self, data: np.ndarray) -> np.ndarray: + try: + import noisereduce + except ImportError as e: + raise ImportError("Please install noisereduce package") from e + + reduced_noise = noisereduce.reduce_noise(y=data, sr=self.sample_rate) + + # add channel back after noise reduction + if self.channel is not None: + # putting the channel back in the data + data[self.channel] = reduced_noise + # updating the data to save + reduced_noise = data + + return reduced_noise
+
+ + + +
+[docs] +class DFN(ReduceNoiseBase): + def __init__( + self, + target_directory: Path, + verbose: bool = True, + silence_threshold: float = None, + pad: bool = True, + atten_lim_db: int = None, + **kwargs, + ): + super().__init__(target_directory, verbose, silence_threshold) + self.pad = pad + self.atten_lim_db = atten_lim_db + self.kwargs = kwargs + + # import required packages + try: + from df.enhance import init_df + except ImportError as e: + raise ImportError("Please install deepfilternet packages") from e + + if self.verbose: + _LOGGER.info("Loading DeepFilterNet2 model.") + + # Load the model: + model, df_state, _ = init_df() + self.model = model + self.df_state = df_state + self.sample_rate = self.df_state.sr() + +
+[docs] + def save_audio(self, audio: np.ndarray, target_path: Path): + try: + from df.enhance import save_audio + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + save_audio( + file=target_path.name, + audio=audio, + sr=self.sample_rate, + output_dir=str(self.target_directory), + )
+ + +
+[docs] + def load_audio(self, file: str) -> torch.Tensor: + try: + from df.enhance import load_audio + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + audio, _ = load_audio(file=file, sr=self.sample_rate, **self.kwargs) + return audio
+ + +
+[docs] + def clean_audio(self, data: torch.Tensor) -> torch.Tensor: + try: + from df.enhance import enhance + except ImportError as e: + raise ImportError("Please install deepfilternet package") from e + return enhance( + model=self.model, + df_state=self.df_state, + audio=data, + pad=self.pad, + atten_lim_db=self.atten_lim_db, + )
+
+ + + +def _multiprocessing_complete_tasks( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + tasks_queue: Queue, + results_queue: Queue, +): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param noise_reduce_type: The noise reduce type to use. + :param noise_reduce_arguments: The noisereduce initialization kwargs. + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + # Initialize the reduce noise object + noise_reducer = noise_reduce_type(**noise_reduce_arguments) + + # Start listening to the tasks queue: + while True: + # Get the audio_file: + audio_file = tasks_queue.get() + if audio_file == _MULTIPROCESSING_STOP_MARK: + break + audio_file = Path(audio_file) + # Apply noise reduction and collect the result: + results_queue.put(noise_reducer.reduce_noise(audio_file=audio_file)) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +
+[docs] +def reduce_noise_dfn( + audio_source: str, + target_directory: str, + pad: bool = True, + atten_lim_db: int = None, + silence_threshold: float = None, + use_multiprocessing: int = 0, + verbose: bool = True, + **kwargs, +): + """ + Reduce noise from audio files using DeepFilterNet. + For more information about the noise reduction algorithm see: + https://github.com/Rikorose/DeepFilterNet + Notice that the saved files are in wav format, even if the original files are in other format. + + :param audio_source: path to audio file or directory of audio files + :param target_directory: path to target directory to save cleaned audio files + :param pad: whether to pad the audio file with zeros before cleaning + :param atten_lim_db: maximum attenuation in dB + :param silence_threshold: the threshold to remove silence from the audio, in dB. If None, no silence removal is + performed. + :param use_multiprocessing: Number of processes to use for cleaning the audio files. + If 0, no multiprocessing is used. + :param verbose: verbosity level. If True, display progress bar and logs. + :param kwargs: additional arguments to pass to torchaudio.load(). For more information see: + https://pytorch.org/audio/stable/generated/torchaudio.load.html + """ + if verbose: + _LOGGER.info("Reducing noise from audio files.") + + # create target directory: + target_directory = _create_target_directory(target_directory) + + # get audio files: + audio_files = _get_audio_files(audio_source) + + noise_reduce_arguments = { + "target_directory": target_directory, + "pad": pad, + "atten_lim_db": atten_lim_db, + "silence_threshold": silence_threshold, + **kwargs, + } + + if use_multiprocessing: + results = _parallel_run( + noise_reduce_type=DFN, + noise_reduce_arguments=noise_reduce_arguments, + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + else: + results = _run( + noise_reduce_type=DFN, + noise_reduce_arguments=noise_reduce_arguments, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + + return _process_results(results, verbose)
+ + + +
+[docs] +def reduce_noise( + audio_source: str, + target_directory: str, + sample_rate: int = 16000, + duration: int = None, + channel: int = None, + silence_threshold: float = None, + use_multiprocessing: int = 0, + verbose: bool = True, +): + """ + Reduce noise from audio file or directory containing audio files. + The audio files must be in .wav format. + The cleaned audio files will be saved in the target_directory. + For information about the noise reduction algorithm see: + https://github.com/timsainb/noisereduce + Notice that the saved files are in wav format, even if the original files are in other format. + + :param audio_source: path to audio file or directory containing audio files + :param target_directory: path to directory to save the cleaned audio files. + :param sample_rate: Number of samples in one second in the audio file. + Pass `None` to keep the original sample rate. + :param duration: Duration of the audio file to clean in seconds. + Pass `None` to keep the original duration. + :param channel: Channel to clean. Pass the number of the channel to clean. + To clean all channels pass None. + :param silence_threshold: The threshold to remove silence from the audio, in dB. + If None, no silence removal is performed. + :param use_multiprocessing: Number of processes to use for cleaning the audio files. + If 0, no multiprocessing is used. + :param verbose: Verbosity level. If True, display progress bar. + """ + if verbose: + _LOGGER.info("Reducing noise from audio files.") + + # create target directory: + target_directory = _create_target_directory(target_directory) + + # get audio files: + audio_files = _get_audio_files(audio_source) + + # Create the reduce noise object: + noise_reduce_arguments = { + "target_directory": target_directory, + "sample_rate": sample_rate, + "duration": duration, + "channel": channel, + "silence_threshold": silence_threshold, + } + + if use_multiprocessing: + results = _parallel_run( + noise_reduce_type=ReduceNoise, + noise_reduce_arguments=noise_reduce_arguments, + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + else: + results = _run( + noise_reduce_type=ReduceNoise, + noise_reduce_arguments=noise_reduce_arguments, + audio_files=audio_files, + description="Noise-reduction", + verbose=verbose, + ) + + return _process_results(results, verbose)
+ + + +def _create_target_directory(target_directory: str) -> str: + target_directory = Path(target_directory) + if not target_directory.exists(): + target_directory.mkdir(parents=True, exist_ok=True) + return str(target_directory) + + +def _get_audio_files(audio_source: str): + audio_source = Path(audio_source) + audio_files = [] + if audio_source.is_dir(): + audio_files = list(audio_source.glob("*.*")) + elif audio_source.is_file(): + audio_files.append(audio_source) + else: + raise ValueError( + f"audio_source must be a file or a directory, got {audio_source}" + ) + return audio_files + + +def _parallel_run( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + n_workers: int, + audio_files: List[Path], + description: str, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run multiple noise reduce workers with multiprocessing to complete the tasks that will be created on the provided + files using the given task creator. + + :param noise_reduce_type: The noise reduce type to use. + :param n_workers: The number of workers to use. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Check the number of workers: + if n_workers > len(audio_files): + _LOGGER.warning( + f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). " + f"Setting the number of workers to {len(audio_files)}." + ) + n_workers = len(audio_files) + + # Initialize the multiprocessing queues: + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={ + "noise_reduce_type": noise_reduce_type, + "noise_reduce_arguments": noise_reduce_arguments, + "tasks_queue": tasks_queue, + "results_queue": results_queue, + }, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + for p in task_completion_processes: + p.start() + + # Put the tasks in the queue: + for audio_file in audio_files: + # tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple()) + tasks_queue.put(audio_file) + + # Put the stop marks in the queue: + for _ in range(n_workers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + # Collect the results: + results = [] + stop_marks_counter = 0 + with tqdm( + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ) as progressbar: + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, str]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + progressbar.update(1) + + # Wait for the processes to finish: + for p in task_completion_processes: + p.join() + + return results + + +def _run( + noise_reduce_type: Type[ReduceNoiseBase], + noise_reduce_arguments: dict, + audio_files: List[Path], + description: str, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run the noise reduce algorithm on the given audio files and collect the results. + + :param noise_reduce_type: The noise reduce type to use. + :param noise_reduce_arguments: The noisereduce initialization kwargs. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Create the reduce noise object: + noise_reducer = noise_reduce_type(**noise_reduce_arguments) + + # Run the noise reduce algorithm on the audio files and collect the results: + results = [] + for audio_file in tqdm( + audio_files, + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ): + results.append(noise_reducer.reduce_noise(audio_file=audio_file)) + + return results + + +def _process_results( + results: List[Tuple[bool, Tuple[str, str]]], verbose: bool +) -> Tuple[dict, dict]: + """ + Process the results of the tasks. + + :param results: The results to process. + :param verbose: Verbosity. + + :returns: The processed results as a tuple of successes and errors. + """ + if verbose: + _LOGGER.info("Summarizing the results.") + successes = {} + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes[result[0]] = result[1] + if verbose: + _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n") + + return successes, errors +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + +
+
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/1.1.0/static/source.html b/functions/master/noise_reduction/1.1.0/static/source.html new file mode 100644 index 00000000..d46b6ee1 --- /dev/null +++ b/functions/master/noise_reduction/1.1.0/static/source.html @@ -0,0 +1,660 @@ + + + + + + + + + + + Source + + + + +
+        
+import logging
+from abc import ABCMeta, abstractmethod
+from multiprocessing import Process, Queue
+from pathlib import Path
+from typing import List, Tuple, Type, Union
+
+import librosa
+import numpy as np
+import torch
+from scipy.io import wavfile
+from tqdm import tqdm
+
+#: The value to send into multiprocessing queues to stop the process:
+_MULTIPROCESSING_STOP_MARK = "STOP"
+
+# Get the global logger:
+try:
+    import mlrun
+
+    _LOGGER = mlrun.get_or_create_ctx("noise_reduce").logger
+except ModuleNotFoundError:
+    _LOGGER = logging.getLogger()
+
+
+class ReduceNoiseBase(metaclass=ABCMeta):
+    """
+    Base class for noise reduction.
+    This class is aimed to be inherited by specific noise reduction algorithms.
+    You must implement the following methods:
+    - clean_audio:  The method to clean the audio, where the noise reduction algorithm is implemented.
+    - save_audio:   The method to save the audio to a file.
+    - load_audio:   The method to load the audio from a file.
+
+    After implementing the above methods, you can use the reduce_noise method to reduce noise from audio files.
+    """
+    def __init__(
+        self,
+        target_directory: Path,
+        verbose: bool = True,
+        silence_threshold: float = None,
+    ):
+        self.target_directory = Path(target_directory)
+        self.verbose = verbose
+        self.silence_threshold = silence_threshold
+
+    def reduce_noise(self, audio_file: Path) -> Tuple[bool, Tuple[str, str]]:
+        """
+        Reduce noise from the given audio file.
+
+        :param audio_file:  The audio file to reduce noise from.
+
+        :returns: A tuple of:
+         - a boolean indicating whether an error occurred
+         - a tuple of:
+            - audio file name
+            - target path in case of success / error message in case of failure.
+        """
+        try:
+            if self.verbose:
+                _LOGGER.info(f"Reducing noise from {audio_file.name}.")
+
+            # Load audio data:
+            audio = self.load_audio(file=str(audio_file))
+
+            # Perform noise reduction:
+            reduced_noise = self.clean_audio(data=audio)
+
+            # Remove silence from the audio if necessary:
+            reduced_noise = self.remove_silence(audio=reduced_noise)
+
+            # Prepare target path:
+            target_path = self.update_to_wav_suffix(audio_file=audio_file)
+
+            # Save file:
+            self.save_audio(
+                audio=reduced_noise,
+                target_path=target_path,
+            )
+
+            if self.verbose:
+                _LOGGER.info(f"Saved cleaned audio file to {target_path}.")
+
+            return False, (audio_file.name, str(target_path))
+        except Exception as exception:
+            if self.verbose:
+                _LOGGER.error(f"Failed to reduce noise from {audio_file.name}.")
+                _LOGGER.error(f"Error: {exception}")
+            # Collect the error:
+            return True, (audio_file.name, str(exception))
+
+    @abstractmethod
+    def clean_audio(self, data) -> Union[np.ndarray, torch.Tensor]:
+        """
+        Clean the audio from noise. Here you should implement the noise reduction algorithm.
+
+        :param data:    The audio data to clean.
+
+        :returns: The cleaned audio.
+        """
+        pass
+
+    @abstractmethod
+    def save_audio(self, audio: np.ndarray, target_path: Path):
+        """
+        Save the audio to a file.
+
+        :param audio:       The audio to save.
+        :param target_path: The target path to save the audio to.
+        """
+        pass
+
+    @abstractmethod
+    def load_audio(self, file: str) -> Tuple[Union[np.ndarray, torch.Tensor], int]:
+        """
+        Load the audio from a file.
+
+        :param file:    The file to load the audio from.
+
+        :returns: A tuple of:
+            - the audio data
+            - the sample rate
+        """
+        pass
+
+    def update_to_wav_suffix(self, audio_file: Path):
+        target_path = self.target_directory / audio_file.name
+        if target_path.suffix != ".wav":
+            old_suffix = target_path.suffix[1:]
+            target_path = target_path.with_stem(target_path.stem + f"_{old_suffix}")
+            return target_path.with_suffix(".wav")
+        else:
+            return target_path
+
+    def remove_silence(
+        self,
+        audio: np.ndarray,
+    ):
+        """
+        Remove silence sections from the audio.
+
+        :param audio:   The audio to remove silence from.
+
+        :returns: The audio without silence.
+        """
+        if self.silence_threshold is None:
+            return audio
+
+        # Get the indices of the non-silent frames:
+        non_silent_indices = librosa.effects.split(
+            y=audio,
+            top_db=self.silence_threshold,
+            frame_length=2048,
+            hop_length=256,
+        )
+
+        # Get the non-silent audio:
+        non_silent_audio = np.concatenate(
+            [audio[:, start:end] for start, end in non_silent_indices], axis=1
+        )
+
+        return non_silent_audio
+
+
+class ReduceNoise(ReduceNoiseBase):
+    def __init__(
+        self,
+        target_directory: Path,
+        verbose: bool = True,
+        silence_threshold: float = None,
+        sample_rate: int = 16000,
+        duration: int = None,
+        channel: int = None,
+    ):
+        super().__init__(target_directory, verbose, silence_threshold)
+        self.sample_rate = sample_rate
+        self.duration = duration
+        self.channel = channel
+
+    def save_audio(self, audio: np.ndarray, target_path: Path):
+        # If the audio has more than one channel, transpose it in order to save it:
+        if len(audio) > 1:
+            audio = audio.T
+
+        wavfile.write(
+            filename=target_path,
+            rate=self.sample_rate,
+            data=audio,
+        )
+
+    def load_audio(self, file: str) -> np.ndarray:
+        data, sr = librosa.load(
+            path=file,
+            sr=self.sample_rate,
+            mono=False,  # keep channels separate
+            duration=self.duration,
+        )
+        # set sample rate:
+        self.sample_rate = int(sr)
+
+        # convert to int with scaling for 16-bit integer
+        data *= 32767 / np.max(np.abs(data))  # re-scaling
+        data = data.astype(np.int16)  # change data type
+
+        # select channel
+        data_to_reduce = data[self.channel] if self.channel is not None else data
+        return data_to_reduce
+
+    def clean_audio(self, data: np.ndarray) -> np.ndarray:
+        try:
+            import noisereduce
+        except ImportError as e:
+            raise ImportError("Please install noisereduce package") from e
+
+        reduced_noise = noisereduce.reduce_noise(y=data, sr=self.sample_rate)
+
+        # add channel back after noise reduction
+        if self.channel is not None:
+            # putting the channel back in the data
+            data[self.channel] = reduced_noise
+            # updating the data to save
+            reduced_noise = data
+
+        return reduced_noise
+
+
+class DFN(ReduceNoiseBase):
+    def __init__(
+        self,
+        target_directory: Path,
+        verbose: bool = True,
+        silence_threshold: float = None,
+        pad: bool = True,
+        atten_lim_db: int = None,
+        **kwargs,
+    ):
+        super().__init__(target_directory, verbose, silence_threshold)
+        self.pad = pad
+        self.atten_lim_db = atten_lim_db
+        self.kwargs = kwargs
+
+        # import required packages
+        try:
+            from df.enhance import init_df
+        except ImportError as e:
+            raise ImportError("Please install deepfilternet packages") from e
+
+        if self.verbose:
+            _LOGGER.info("Loading DeepFilterNet2 model.")
+
+        # Load the model:
+        model, df_state, _ = init_df()
+        self.model = model
+        self.df_state = df_state
+        self.sample_rate = self.df_state.sr()
+
+    def save_audio(self, audio: np.ndarray, target_path: Path):
+        try:
+            from df.enhance import save_audio
+        except ImportError as e:
+            raise ImportError("Please install deepfilternet package") from e
+        save_audio(
+            file=target_path.name,
+            audio=audio,
+            sr=self.sample_rate,
+            output_dir=str(self.target_directory),
+        )
+
+    def load_audio(self, file: str) -> torch.Tensor:
+        try:
+            from df.enhance import load_audio
+        except ImportError as e:
+            raise ImportError("Please install deepfilternet package") from e
+        audio, _ = load_audio(file=file, sr=self.sample_rate, **self.kwargs)
+        return audio
+
+    def clean_audio(self, data: torch.Tensor) -> torch.Tensor:
+        try:
+            from df.enhance import enhance
+        except ImportError as e:
+            raise ImportError("Please install deepfilternet package") from e
+        return enhance(
+            model=self.model,
+            df_state=self.df_state,
+            audio=data,
+            pad=self.pad,
+            atten_lim_db=self.atten_lim_db,
+        )
+
+
+def _multiprocessing_complete_tasks(
+    noise_reduce_type: Type[ReduceNoiseBase],
+    noise_reduce_arguments: dict,
+    tasks_queue: Queue,
+    results_queue: Queue,
+):
+    """
+    Complete the tasks in the given queue and put the results in the given results queue. The function will stop when
+    the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process.
+
+    :param noise_reduce_type:       The noise reduce type to use.
+    :param noise_reduce_arguments:  The noisereduce initialization kwargs.
+    :param tasks_queue:             A queue to get the tasks from.
+    :param results_queue:           A queue to put the results in.
+    """
+    # Initialize the reduce noise object
+    noise_reducer = noise_reduce_type(**noise_reduce_arguments)
+
+    # Start listening to the tasks queue:
+    while True:
+        # Get the audio_file:
+        audio_file = tasks_queue.get()
+        if audio_file == _MULTIPROCESSING_STOP_MARK:
+            break
+        audio_file = Path(audio_file)
+        # Apply noise reduction and collect the result:
+        results_queue.put(noise_reducer.reduce_noise(audio_file=audio_file))
+
+    # Mark the end of the tasks:
+    results_queue.put(_MULTIPROCESSING_STOP_MARK)
+
+
+def reduce_noise_dfn(
+    audio_source: str,
+    target_directory: str,
+    pad: bool = True,
+    atten_lim_db: int = None,
+    silence_threshold: float = None,
+    use_multiprocessing: int = 0,
+    verbose: bool = True,
+    **kwargs,
+):
+    """
+    Reduce noise from audio files using DeepFilterNet.
+    For more information about the noise reduction algorithm see:
+    https://github.com/Rikorose/DeepFilterNet
+    Notice that the saved files are in wav format, even if the original files are in other format.
+
+    :param audio_source:        path to audio file or directory of audio files
+    :param target_directory:    path to target directory to save cleaned audio files
+    :param pad:                 whether to pad the audio file with zeros before cleaning
+    :param atten_lim_db:        maximum attenuation in dB
+    :param silence_threshold:   the threshold to remove silence from the audio, in dB. If None, no silence removal is
+                                performed.
+    :param use_multiprocessing: Number of processes to use for cleaning the audio files.
+                                If 0, no multiprocessing is used.
+    :param verbose:             verbosity level. If True, display progress bar and logs.
+    :param kwargs:              additional arguments to pass to torchaudio.load(). For more information see:
+                                https://pytorch.org/audio/stable/generated/torchaudio.load.html
+    """
+    if verbose:
+        _LOGGER.info("Reducing noise from audio files.")
+
+    # create target directory:
+    target_directory = _create_target_directory(target_directory)
+
+    # get audio files:
+    audio_files = _get_audio_files(audio_source)
+
+    noise_reduce_arguments = {
+        "target_directory": target_directory,
+        "pad": pad,
+        "atten_lim_db": atten_lim_db,
+        "silence_threshold": silence_threshold,
+        **kwargs,
+    }
+
+    if use_multiprocessing:
+        results = _parallel_run(
+            noise_reduce_type=DFN,
+            noise_reduce_arguments=noise_reduce_arguments,
+            n_workers=use_multiprocessing,
+            audio_files=audio_files,
+            description="Noise-reduction",
+            verbose=verbose,
+        )
+    else:
+        results = _run(
+            noise_reduce_type=DFN,
+            noise_reduce_arguments=noise_reduce_arguments,
+            audio_files=audio_files,
+            description="Noise-reduction",
+            verbose=verbose,
+        )
+
+    return _process_results(results, verbose)
+
+
+def reduce_noise(
+    audio_source: str,
+    target_directory: str,
+    sample_rate: int = 16000,
+    duration: int = None,
+    channel: int = None,
+    silence_threshold: float = None,
+    use_multiprocessing: int = 0,
+    verbose: bool = True,
+):
+    """
+    Reduce noise from audio file or directory containing audio files.
+    The audio files must be in .wav format.
+    The cleaned audio files will be saved in the target_directory.
+    For information about the noise reduction algorithm see:
+    https://github.com/timsainb/noisereduce
+    Notice that the saved files are in wav format, even if the original files are in other format.
+
+    :param audio_source:        path to audio file or directory containing audio files
+    :param target_directory:    path to directory to save the cleaned audio files.
+    :param sample_rate:         Number of samples in one second in the audio file.
+                                Pass `None` to keep the original sample rate.
+    :param duration:            Duration of the audio file to clean in seconds.
+                                Pass `None` to keep the original duration.
+    :param channel:             Channel to clean. Pass the number of the channel to clean.
+                                To clean all channels pass None.
+    :param silence_threshold:   The threshold to remove silence from the audio, in dB.
+                                If None, no silence removal is performed.
+    :param use_multiprocessing: Number of processes to use for cleaning the audio files.
+                                If 0, no multiprocessing is used.
+    :param verbose:             Verbosity level. If True, display progress bar.
+    """
+    if verbose:
+        _LOGGER.info("Reducing noise from audio files.")
+
+    # create target directory:
+    target_directory = _create_target_directory(target_directory)
+
+    # get audio files:
+    audio_files = _get_audio_files(audio_source)
+
+    # Create the reduce noise object:
+    noise_reduce_arguments = {
+        "target_directory": target_directory,
+        "sample_rate": sample_rate,
+        "duration": duration,
+        "channel": channel,
+        "silence_threshold": silence_threshold,
+    }
+
+    if use_multiprocessing:
+        results = _parallel_run(
+            noise_reduce_type=ReduceNoise,
+            noise_reduce_arguments=noise_reduce_arguments,
+            n_workers=use_multiprocessing,
+            audio_files=audio_files,
+            description="Noise-reduction",
+            verbose=verbose,
+        )
+    else:
+        results = _run(
+            noise_reduce_type=ReduceNoise,
+            noise_reduce_arguments=noise_reduce_arguments,
+            audio_files=audio_files,
+            description="Noise-reduction",
+            verbose=verbose,
+        )
+
+    return _process_results(results, verbose)
+
+
+def _create_target_directory(target_directory: str) -> str:
+    target_directory = Path(target_directory)
+    if not target_directory.exists():
+        target_directory.mkdir(parents=True, exist_ok=True)
+    return str(target_directory)
+
+
+def _get_audio_files(audio_source: str):
+    audio_source = Path(audio_source)
+    audio_files = []
+    if audio_source.is_dir():
+        audio_files = list(audio_source.glob("*.*"))
+    elif audio_source.is_file():
+        audio_files.append(audio_source)
+    else:
+        raise ValueError(
+            f"audio_source must be a file or a directory, got {audio_source}"
+        )
+    return audio_files
+
+
+def _parallel_run(
+    noise_reduce_type: Type[ReduceNoiseBase],
+    noise_reduce_arguments: dict,
+    n_workers: int,
+    audio_files: List[Path],
+    description: str,
+    verbose: bool,
+) -> List[Tuple[bool, Tuple[str, str]]]:
+    """
+    Run multiple noise reduce workers with multiprocessing to complete the tasks that will be created on the provided
+    files using the given task creator.
+
+    :param noise_reduce_type:   The noise reduce type to use.
+    :param n_workers:           The number of workers to use.
+    :param audio_files:         The audio files to use.
+    :param description:         The description to use for the progress bar.
+    :param verbose:             Verbosity.
+
+    :returns: The collected results.
+    """
+    # Check the number of workers:
+    if n_workers > len(audio_files):
+        _LOGGER.warning(
+            f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). "
+            f"Setting the number of workers to {len(audio_files)}."
+        )
+        n_workers = len(audio_files)
+
+    # Initialize the multiprocessing queues:
+    tasks_queue = Queue()
+    results_queue = Queue()
+
+    # Initialize the multiprocessing processes:
+    task_completion_processes = [
+        Process(
+            target=_multiprocessing_complete_tasks,
+            kwargs={
+                "noise_reduce_type": noise_reduce_type,
+                "noise_reduce_arguments": noise_reduce_arguments,
+                "tasks_queue": tasks_queue,
+                "results_queue": results_queue,
+            },
+        )
+        for _ in range(n_workers)
+    ]
+
+    # Start the multiprocessing processes:
+    for p in task_completion_processes:
+        p.start()
+
+    # Put the tasks in the queue:
+    for audio_file in audio_files:
+        # tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple())
+        tasks_queue.put(audio_file)
+
+    # Put the stop marks in the queue:
+    for _ in range(n_workers):
+        tasks_queue.put(_MULTIPROCESSING_STOP_MARK)
+
+    # Collect the results:
+    results = []
+    stop_marks_counter = 0
+    with tqdm(
+        desc=description,
+        unit="file",
+        total=len(audio_files),
+        disable=not verbose,
+    ) as progressbar:
+        while True:
+            # Get a result from the queue:
+            result: Tuple[bool, Tuple[str, str]] = results_queue.get()
+            if result == _MULTIPROCESSING_STOP_MARK:
+                stop_marks_counter += 1
+                if stop_marks_counter == n_workers:
+                    break
+            else:
+                # Collect the result:
+                results.append(result)
+                progressbar.update(1)
+
+    # Wait for the processes to finish:
+    for p in task_completion_processes:
+        p.join()
+
+    return results
+
+
+def _run(
+    noise_reduce_type: Type[ReduceNoiseBase],
+    noise_reduce_arguments: dict,
+    audio_files: List[Path],
+    description: str,
+    verbose: bool,
+) -> List[Tuple[bool, Tuple[str, str]]]:
+    """
+    Run the noise reduce algorithm on the given audio files and collect the results.
+
+    :param noise_reduce_type:       The noise reduce type to use.
+    :param noise_reduce_arguments:  The noisereduce initialization kwargs.
+    :param audio_files:             The audio files to use.
+    :param description:             The description to use for the progress bar.
+    :param verbose:                 Verbosity.
+
+    :returns: The collected results.
+    """
+    # Create the reduce noise object:
+    noise_reducer = noise_reduce_type(**noise_reduce_arguments)
+
+    # Run the noise reduce algorithm on the audio files and collect the results:
+    results = []
+    for audio_file in tqdm(
+        audio_files,
+        desc=description,
+        unit="file",
+        total=len(audio_files),
+        disable=not verbose,
+    ):
+        results.append(noise_reducer.reduce_noise(audio_file=audio_file))
+
+    return results
+
+
+def _process_results(
+    results: List[Tuple[bool, Tuple[str, str]]], verbose: bool
+) -> Tuple[dict, dict]:
+    """
+    Process the results of the tasks.
+
+    :param results: The results to process.
+    :param verbose: Verbosity.
+
+    :returns: The processed results as a tuple of successes and errors.
+    """
+    if verbose:
+        _LOGGER.info("Summarizing the results.")
+    successes = {}
+    errors = {}
+    for is_error, result in results:
+        if is_error:
+            errors[result[0]] = result[1]
+        else:
+            successes[result[0]] = result[1]
+    if verbose:
+        _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n")
+
+    return successes, errors
+
+        
+    
+ + \ No newline at end of file diff --git a/functions/master/noise_reduction/latest/src/function.yaml b/functions/master/noise_reduction/latest/src/function.yaml index e03729a9..d6d33b8d 100644 --- a/functions/master/noise_reduction/latest/src/function.yaml +++ b/functions/master/noise_reduction/latest/src/function.yaml @@ -1,30 +1,9 @@ -kind: job -metadata: - name: noise-reduction - tag: '' - hash: cbf6498dca0358810ddaea3baa0e246b7874ea1d - project: '' - labels: - author: yonatans - categories: [] spec: - command: '' - args: [] - image: '' - build: - functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK - base_image: mlrun/mlrun - commands: [] - code_origin: '' - origin_filename: '' - requirements: - - librosa - - noisereduce - - deepfilternet - - torchaudio>=2.1.2 entry_points: reduce_noise: + has_kwargs: false name: reduce_noise + has_varargs: false doc: 'Reduce noise from audio file or directory containing audio files. The audio files must be in .wav format. @@ -73,24 +52,23 @@ spec: type: bool doc: Verbosity level. If True, display progress bar. default: true - outputs: [] lineno: 388 - has_varargs: false - has_kwargs: false clean_audio: + has_kwargs: false name: clean_audio + has_varargs: false + outputs: + - type: torch.Tensor doc: '' parameters: - name: self - name: data type: Tensor - outputs: - - type: torch.Tensor lineno: 276 - has_varargs: false - has_kwargs: false save_audio: + has_kwargs: false name: save_audio + has_varargs: false doc: '' parameters: - name: self @@ -98,48 +76,46 @@ spec: type: ndarray - name: target_path type: Path - outputs: [] lineno: 256 - has_varargs: false - has_kwargs: false load_audio: + has_kwargs: false name: load_audio + has_varargs: false + outputs: + - type: torch.Tensor doc: '' parameters: - name: self - name: file type: str - outputs: - - type: torch.Tensor lineno: 268 - has_varargs: false - has_kwargs: false update_to_wav_suffix: + has_kwargs: false name: update_to_wav_suffix + has_varargs: false doc: '' parameters: - name: self - name: audio_file type: Path - outputs: [] lineno: 125 - has_varargs: false - has_kwargs: false remove_silence: + has_kwargs: false name: remove_silence + has_varargs: false + outputs: + - doc: The audio without silence. doc: Remove silence sections from the audio. parameters: - name: self - name: audio type: ndarray doc: The audio to remove silence from. - outputs: - - doc: The audio without silence. lineno: 134 - has_varargs: false - has_kwargs: false reduce_noise_dfn: + has_kwargs: true name: reduce_noise_dfn + has_varargs: false doc: 'Reduce noise from audio files using DeepFilterNet. For more information about the noise reduction algorithm see: @@ -177,18 +153,27 @@ spec: type: bool doc: verbosity level. If True, display progress bar and logs. default: true - outputs: [] lineno: 322 - has_varargs: false - has_kwargs: true + build: + code_origin: '' + base_image: mlrun/mlrun + requirements: + - librosa + - noisereduce + - deepfilternet + - torchaudio>=2.1.2 + functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK + origin_filename: '' description: Reduce noise from audio files + command: '' + image: '' default_handler: reduce_noise disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} +metadata: + name: noise-reduction + tag: '' + categories: + - data-preparation + - audio +kind: job verbose: false diff --git a/functions/master/noise_reduction/latest/src/item.yaml b/functions/master/noise_reduction/latest/src/item.yaml index 8ddc63f4..f748d558 100644 --- a/functions/master/noise_reduction/latest/src/item.yaml +++ b/functions/master/noise_reduction/latest/src/item.yaml @@ -1,7 +1,7 @@ apiVersion: v1 categories: - data-preparation - - machine-learning + - audio description: Reduce noise from audio files doc: '' example: noise_reduction.ipynb @@ -11,7 +11,7 @@ icon: '' labels: author: yonatans maintainers: [] -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: noise-reduction platformVersion: 3.5.3 spec: @@ -26,4 +26,4 @@ spec: torchaudio>=2.1.2, ] url: '' -version: 1.0.0 \ No newline at end of file +version: 1.1.0 \ No newline at end of file diff --git a/functions/master/noise_reduction/latest/static/documentation.html b/functions/master/noise_reduction/latest/static/documentation.html index 748a3e9b..a772e518 100644 --- a/functions/master/noise_reduction/latest/static/documentation.html +++ b/functions/master/noise_reduction/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/noise_reduction/latest/static/example.html b/functions/master/noise_reduction/latest/static/example.html index c3a04295..7ad47bf9 100644 --- a/functions/master/noise_reduction/latest/static/example.html +++ b/functions/master/noise_reduction/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/noise_reduction/latest/static/function.html b/functions/master/noise_reduction/latest/static/function.html index 14b402f5..8c061036 100644 --- a/functions/master/noise_reduction/latest/static/function.html +++ b/functions/master/noise_reduction/latest/static/function.html @@ -28,33 +28,12 @@
         
-kind: job
-metadata:
-  name: noise-reduction
-  tag: ''
-  hash: cbf6498dca0358810ddaea3baa0e246b7874ea1d
-  project: ''
-  labels:
-    author: yonatans
-  categories: []
 spec:
-  command: ''
-  args: []
-  image: ''
-  build:
-    functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
-    base_image: mlrun/mlrun
-    commands: []
-    code_origin: ''
-    origin_filename: ''
-    requirements:
-    - librosa
-    - noisereduce
-    - deepfilternet
-    - torchaudio>=2.1.2
   entry_points:
     reduce_noise:
+      has_kwargs: false
       name: reduce_noise
+      has_varargs: false
       doc: 'Reduce noise from audio file or directory containing audio files.
 
         The audio files must be in .wav format.
@@ -103,24 +82,23 @@
         type: bool
         doc: Verbosity level. If True, display progress bar.
         default: true
-      outputs: []
       lineno: 388
-      has_varargs: false
-      has_kwargs: false
     clean_audio:
+      has_kwargs: false
       name: clean_audio
+      has_varargs: false
+      outputs:
+      - type: torch.Tensor
       doc: ''
       parameters:
       - name: self
       - name: data
         type: Tensor
-      outputs:
-      - type: torch.Tensor
       lineno: 276
-      has_varargs: false
-      has_kwargs: false
     save_audio:
+      has_kwargs: false
       name: save_audio
+      has_varargs: false
       doc: ''
       parameters:
       - name: self
@@ -128,48 +106,46 @@
         type: ndarray
       - name: target_path
         type: Path
-      outputs: []
       lineno: 256
-      has_varargs: false
-      has_kwargs: false
     load_audio:
+      has_kwargs: false
       name: load_audio
+      has_varargs: false
+      outputs:
+      - type: torch.Tensor
       doc: ''
       parameters:
       - name: self
       - name: file
         type: str
-      outputs:
-      - type: torch.Tensor
       lineno: 268
-      has_varargs: false
-      has_kwargs: false
     update_to_wav_suffix:
+      has_kwargs: false
       name: update_to_wav_suffix
+      has_varargs: false
       doc: ''
       parameters:
       - name: self
       - name: audio_file
         type: Path
-      outputs: []
       lineno: 125
-      has_varargs: false
-      has_kwargs: false
     remove_silence:
+      has_kwargs: false
       name: remove_silence
+      has_varargs: false
+      outputs:
+      - doc: The audio without silence.
       doc: Remove silence sections from the audio.
       parameters:
       - name: self
       - name: audio
         type: ndarray
         doc: The audio to remove silence from.
-      outputs:
-      - doc: The audio without silence.
       lineno: 134
-      has_varargs: false
-      has_kwargs: false
     reduce_noise_dfn:
+      has_kwargs: true
       name: reduce_noise_dfn
+      has_varargs: false
       doc: 'Reduce noise from audio files using DeepFilterNet.
 
         For more information about the noise reduction algorithm see:
@@ -207,20 +183,29 @@
         type: bool
         doc: verbosity level. If True, display progress bar and logs.
         default: true
-      outputs: []
       lineno: 322
-      has_varargs: false
-      has_kwargs: true
+  build:
+    code_origin: ''
+    base_image: mlrun/mlrun
+    requirements:
+    - librosa
+    - noisereduce
+    - deepfilternet
+    - torchaudio>=2.1.2
+    functionSourceCode: aW1wb3J0IGxvZ2dpbmcKZnJvbSBhYmMgaW1wb3J0IEFCQ01ldGEsIGFic3RyYWN0bWV0aG9kCmZyb20gbXVsdGlwcm9jZXNzaW5nIGltcG9ydCBQcm9jZXNzLCBRdWV1ZQpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFR1cGxlLCBUeXBlLCBVbmlvbgoKaW1wb3J0IGxpYnJvc2EKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCB0b3JjaApmcm9tIHNjaXB5LmlvIGltcG9ydCB3YXZmaWxlCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIzogVGhlIHZhbHVlIHRvIHNlbmQgaW50byBtdWx0aXByb2Nlc3NpbmcgcXVldWVzIHRvIHN0b3AgdGhlIHByb2Nlc3M6Cl9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLID0gIlNUT1AiCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKdHJ5OgogICAgaW1wb3J0IG1scnVuCgogICAgX0xPR0dFUiA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KCJub2lzZV9yZWR1Y2UiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmNsYXNzIFJlZHVjZU5vaXNlQmFzZShtZXRhY2xhc3M9QUJDTWV0YSk6CiAgICAiIiIKICAgIEJhc2UgY2xhc3MgZm9yIG5vaXNlIHJlZHVjdGlvbi4KICAgIFRoaXMgY2xhc3MgaXMgYWltZWQgdG8gYmUgaW5oZXJpdGVkIGJ5IHNwZWNpZmljIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG1zLgogICAgWW91IG11c3QgaW1wbGVtZW50IHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKICAgIC0gY2xlYW5fYXVkaW86ICBUaGUgbWV0aG9kIHRvIGNsZWFuIHRoZSBhdWRpbywgd2hlcmUgdGhlIG5vaXNlIHJlZHVjdGlvbiBhbGdvcml0aG0gaXMgaW1wbGVtZW50ZWQuCiAgICAtIHNhdmVfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBzYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCiAgICAtIGxvYWRfYXVkaW86ICAgVGhlIG1ldGhvZCB0byBsb2FkIHRoZSBhdWRpbyBmcm9tIGEgZmlsZS4KCiAgICBBZnRlciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIG1ldGhvZHMsIHlvdSBjYW4gdXNlIHRoZSByZWR1Y2Vfbm9pc2UgbWV0aG9kIHRvIHJlZHVjZSBub2lzZSBmcm9tIGF1ZGlvIGZpbGVzLgogICAgIiIiCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5OiBQYXRoLAogICAgICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAogICAgICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICApOgogICAgICAgIHNlbGYudGFyZ2V0X2RpcmVjdG9yeSA9IFBhdGgodGFyZ2V0X2RpcmVjdG9yeSkKICAgICAgICBzZWxmLnZlcmJvc2UgPSB2ZXJib3NlCiAgICAgICAgc2VsZi5zaWxlbmNlX3RocmVzaG9sZCA9IHNpbGVuY2VfdGhyZXNob2xkCgogICAgZGVmIHJlZHVjZV9ub2lzZShzZWxmLCBhdWRpb19maWxlOiBQYXRoKSAtPiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dOgogICAgICAgICIiIgogICAgICAgIFJlZHVjZSBub2lzZSBmcm9tIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogIFRoZSBhdWRpbyBmaWxlIHRvIHJlZHVjZSBub2lzZSBmcm9tLgoKICAgICAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKICAgICAgICAgLSBhIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIGFuIGVycm9yIG9jY3VycmVkCiAgICAgICAgIC0gYSB0dXBsZSBvZjoKICAgICAgICAgICAgLSBhdWRpbyBmaWxlIG5hbWUKICAgICAgICAgICAgLSB0YXJnZXQgcGF0aCBpbiBjYXNlIG9mIHN1Y2Nlc3MgLyBlcnJvciBtZXNzYWdlIGluIGNhc2Ugb2YgZmFpbHVyZS4KICAgICAgICAiIiIKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlJlZHVjaW5nIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKCiAgICAgICAgICAgICMgTG9hZCBhdWRpbyBkYXRhOgogICAgICAgICAgICBhdWRpbyA9IHNlbGYubG9hZF9hdWRpbyhmaWxlPXN0cihhdWRpb19maWxlKSkKCiAgICAgICAgICAgICMgUGVyZm9ybSBub2lzZSByZWR1Y3Rpb246CiAgICAgICAgICAgIHJlZHVjZWRfbm9pc2UgPSBzZWxmLmNsZWFuX2F1ZGlvKGRhdGE9YXVkaW8pCgogICAgICAgICAgICAjIFJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvIGlmIG5lY2Vzc2FyeToKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IHNlbGYucmVtb3ZlX3NpbGVuY2UoYXVkaW89cmVkdWNlZF9ub2lzZSkKCiAgICAgICAgICAgICMgUHJlcGFyZSB0YXJnZXQgcGF0aDoKICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSBzZWxmLnVwZGF0ZV90b193YXZfc3VmZml4KGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkKCiAgICAgICAgICAgICMgU2F2ZSBmaWxlOgogICAgICAgICAgICBzZWxmLnNhdmVfYXVkaW8oCiAgICAgICAgICAgICAgICBhdWRpbz1yZWR1Y2VkX25vaXNlLAogICAgICAgICAgICAgICAgdGFyZ2V0X3BhdGg9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgICkKCiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbyhmIlNhdmVkIGNsZWFuZWQgYXVkaW8gZmlsZSB0byB7dGFyZ2V0X3BhdGh9LiIpCgogICAgICAgICAgICByZXR1cm4gRmFsc2UsIChhdWRpb19maWxlLm5hbWUsIHN0cih0YXJnZXRfcGF0aCkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgIGlmIHNlbGYudmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJGYWlsZWQgdG8gcmVkdWNlIG5vaXNlIGZyb20ge2F1ZGlvX2ZpbGUubmFtZX0uIikKICAgICAgICAgICAgICAgIF9MT0dHRVIuZXJyb3IoZiJFcnJvcjoge2V4Y2VwdGlvbn0iKQogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXR1cm4gVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpCgogICAgQGFic3RyYWN0bWV0aG9kCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YSkgLT4gVW5pb25bbnAubmRhcnJheSwgdG9yY2guVGVuc29yXToKICAgICAgICAiIiIKICAgICAgICBDbGVhbiB0aGUgYXVkaW8gZnJvbSBub2lzZS4gSGVyZSB5b3Ugc2hvdWxkIGltcGxlbWVudCB0aGUgbm9pc2UgcmVkdWN0aW9uIGFsZ29yaXRobS4KCiAgICAgICAgOnBhcmFtIGRhdGE6ICAgIFRoZSBhdWRpbyBkYXRhIHRvIGNsZWFuLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNsZWFuZWQgYXVkaW8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIHNhdmVfYXVkaW8oc2VsZiwgYXVkaW86IG5wLm5kYXJyYXksIHRhcmdldF9wYXRoOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBTYXZlIHRoZSBhdWRpbyB0byBhIGZpbGUuCgogICAgICAgIDpwYXJhbSBhdWRpbzogICAgICAgVGhlIGF1ZGlvIHRvIHNhdmUuCiAgICAgICAgOnBhcmFtIHRhcmdldF9wYXRoOiBUaGUgdGFyZ2V0IHBhdGggdG8gc2F2ZSB0aGUgYXVkaW8gdG8uCiAgICAgICAgIiIiCiAgICAgICAgcGFzcwoKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBUdXBsZVtVbmlvbltucC5uZGFycmF5LCB0b3JjaC5UZW5zb3JdLCBpbnRdOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIGF1ZGlvIGZyb20gYSBmaWxlLgoKICAgICAgICA6cGFyYW0gZmlsZTogICAgVGhlIGZpbGUgdG8gbG9hZCB0aGUgYXVkaW8gZnJvbS4KCiAgICAgICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CiAgICAgICAgICAgIC0gdGhlIGF1ZGlvIGRhdGEKICAgICAgICAgICAgLSB0aGUgc2FtcGxlIHJhdGUKICAgICAgICAiIiIKICAgICAgICBwYXNzCgogICAgZGVmIHVwZGF0ZV90b193YXZfc3VmZml4KHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgpOgogICAgICAgIHRhcmdldF9wYXRoID0gc2VsZi50YXJnZXRfZGlyZWN0b3J5IC8gYXVkaW9fZmlsZS5uYW1lCiAgICAgICAgaWYgdGFyZ2V0X3BhdGguc3VmZml4ICE9ICIud2F2IjoKICAgICAgICAgICAgb2xkX3N1ZmZpeCA9IHRhcmdldF9wYXRoLnN1ZmZpeFsxOl0KICAgICAgICAgICAgdGFyZ2V0X3BhdGggPSB0YXJnZXRfcGF0aC53aXRoX3N0ZW0odGFyZ2V0X3BhdGguc3RlbSArIGYiX3tvbGRfc3VmZml4fSIpCiAgICAgICAgICAgIHJldHVybiB0YXJnZXRfcGF0aC53aXRoX3N1ZmZpeCgiLndhdiIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIHRhcmdldF9wYXRoCgogICAgZGVmIHJlbW92ZV9zaWxlbmNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW86IG5wLm5kYXJyYXksCiAgICApOgogICAgICAgICIiIgogICAgICAgIFJlbW92ZSBzaWxlbmNlIHNlY3Rpb25zIGZyb20gdGhlIGF1ZGlvLgoKICAgICAgICA6cGFyYW0gYXVkaW86ICAgVGhlIGF1ZGlvIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgYXVkaW8gd2l0aG91dCBzaWxlbmNlLgogICAgICAgICIiIgogICAgICAgIGlmIHNlbGYuc2lsZW5jZV90aHJlc2hvbGQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgICAgICMgR2V0IHRoZSBpbmRpY2VzIG9mIHRoZSBub24tc2lsZW50IGZyYW1lczoKICAgICAgICBub25fc2lsZW50X2luZGljZXMgPSBsaWJyb3NhLmVmZmVjdHMuc3BsaXQoCiAgICAgICAgICAgIHk9YXVkaW8sCiAgICAgICAgICAgIHRvcF9kYj1zZWxmLnNpbGVuY2VfdGhyZXNob2xkLAogICAgICAgICAgICBmcmFtZV9sZW5ndGg9MjA0OCwKICAgICAgICAgICAgaG9wX2xlbmd0aD0yNTYsCiAgICAgICAgKQoKICAgICAgICAjIEdldCB0aGUgbm9uLXNpbGVudCBhdWRpbzoKICAgICAgICBub25fc2lsZW50X2F1ZGlvID0gbnAuY29uY2F0ZW5hdGUoCiAgICAgICAgICAgIFthdWRpb1s6LCBzdGFydDplbmRdIGZvciBzdGFydCwgZW5kIGluIG5vbl9zaWxlbnRfaW5kaWNlc10sIGF4aXM9MQogICAgICAgICkKCiAgICAgICAgcmV0dXJuIG5vbl9zaWxlbnRfYXVkaW8KCgpjbGFzcyBSZWR1Y2VOb2lzZShSZWR1Y2VOb2lzZUJhc2UpOgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgdGFyZ2V0X2RpcmVjdG9yeTogUGF0aCwKICAgICAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgICAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgICAgIHNhbXBsZV9yYXRlOiBpbnQgPSAxNjAwMCwKICAgICAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgICAgICBjaGFubmVsOiBpbnQgPSBOb25lLAogICAgKToKICAgICAgICBzdXBlcigpLl9faW5pdF9fKHRhcmdldF9kaXJlY3RvcnksIHZlcmJvc2UsIHNpbGVuY2VfdGhyZXNob2xkKQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzYW1wbGVfcmF0ZQogICAgICAgIHNlbGYuZHVyYXRpb24gPSBkdXJhdGlvbgogICAgICAgIHNlbGYuY2hhbm5lbCA9IGNoYW5uZWwKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgICMgSWYgdGhlIGF1ZGlvIGhhcyBtb3JlIHRoYW4gb25lIGNoYW5uZWwsIHRyYW5zcG9zZSBpdCBpbiBvcmRlciB0byBzYXZlIGl0OgogICAgICAgIGlmIGxlbihhdWRpbykgPiAxOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLlQKCiAgICAgICAgd2F2ZmlsZS53cml0ZSgKICAgICAgICAgICAgZmlsZW5hbWU9dGFyZ2V0X3BhdGgsCiAgICAgICAgICAgIHJhdGU9c2VsZi5zYW1wbGVfcmF0ZSwKICAgICAgICAgICAgZGF0YT1hdWRpbywKICAgICAgICApCgogICAgZGVmIGxvYWRfYXVkaW8oc2VsZiwgZmlsZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgIGRhdGEsIHNyID0gbGlicm9zYS5sb2FkKAogICAgICAgICAgICBwYXRoPWZpbGUsCiAgICAgICAgICAgIHNyPXNlbGYuc2FtcGxlX3JhdGUsCiAgICAgICAgICAgIG1vbm89RmFsc2UsICAjIGtlZXAgY2hhbm5lbHMgc2VwYXJhdGUKICAgICAgICAgICAgZHVyYXRpb249c2VsZi5kdXJhdGlvbiwKICAgICAgICApCiAgICAgICAgIyBzZXQgc2FtcGxlIHJhdGU6CiAgICAgICAgc2VsZi5zYW1wbGVfcmF0ZSA9IGludChzcikKCiAgICAgICAgIyBjb252ZXJ0IHRvIGludCB3aXRoIHNjYWxpbmcgZm9yIDE2LWJpdCBpbnRlZ2VyCiAgICAgICAgZGF0YSAqPSAzMjc2NyAvIG5wLm1heChucC5hYnMoZGF0YSkpICAjIHJlLXNjYWxpbmcKICAgICAgICBkYXRhID0gZGF0YS5hc3R5cGUobnAuaW50MTYpICAjIGNoYW5nZSBkYXRhIHR5cGUKCiAgICAgICAgIyBzZWxlY3QgY2hhbm5lbAogICAgICAgIGRhdGFfdG9fcmVkdWNlID0gZGF0YVtzZWxmLmNoYW5uZWxdIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZSBlbHNlIGRhdGEKICAgICAgICByZXR1cm4gZGF0YV90b19yZWR1Y2UKCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogbnAubmRhcnJheSkgLT4gbnAubmRhcnJheToKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBub2lzZXJlZHVjZQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgbm9pc2VyZWR1Y2UgcGFja2FnZSIpIGZyb20gZQoKICAgICAgICByZWR1Y2VkX25vaXNlID0gbm9pc2VyZWR1Y2UucmVkdWNlX25vaXNlKHk9ZGF0YSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSkKCiAgICAgICAgIyBhZGQgY2hhbm5lbCBiYWNrIGFmdGVyIG5vaXNlIHJlZHVjdGlvbgogICAgICAgIGlmIHNlbGYuY2hhbm5lbCBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyBwdXR0aW5nIHRoZSBjaGFubmVsIGJhY2sgaW4gdGhlIGRhdGEKICAgICAgICAgICAgZGF0YVtzZWxmLmNoYW5uZWxdID0gcmVkdWNlZF9ub2lzZQogICAgICAgICAgICAjIHVwZGF0aW5nIHRoZSBkYXRhIHRvIHNhdmUKICAgICAgICAgICAgcmVkdWNlZF9ub2lzZSA9IGRhdGEKCiAgICAgICAgcmV0dXJuIHJlZHVjZWRfbm9pc2UKCgpjbGFzcyBERk4oUmVkdWNlTm9pc2VCYXNlKToKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIHRhcmdldF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAgICAgc2lsZW5jZV90aHJlc2hvbGQ6IGZsb2F0ID0gTm9uZSwKICAgICAgICBwYWQ6IGJvb2wgPSBUcnVlLAogICAgICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgICAgICAqKmt3YXJncywKICAgICk6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyh0YXJnZXRfZGlyZWN0b3J5LCB2ZXJib3NlLCBzaWxlbmNlX3RocmVzaG9sZCkKICAgICAgICBzZWxmLnBhZCA9IHBhZAogICAgICAgIHNlbGYuYXR0ZW5fbGltX2RiID0gYXR0ZW5fbGltX2RiCiAgICAgICAgc2VsZi5rd2FyZ3MgPSBrd2FyZ3MKCiAgICAgICAgIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgaW5pdF9kZgogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlcyIpIGZyb20gZQoKICAgICAgICBpZiBzZWxmLnZlcmJvc2U6CiAgICAgICAgICAgIF9MT0dHRVIuaW5mbygiTG9hZGluZyBEZWVwRmlsdGVyTmV0MiBtb2RlbC4iKQoKICAgICAgICAjIExvYWQgdGhlIG1vZGVsOgogICAgICAgIG1vZGVsLCBkZl9zdGF0ZSwgXyA9IGluaXRfZGYoKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZGZfc3RhdGUgPSBkZl9zdGF0ZQogICAgICAgIHNlbGYuc2FtcGxlX3JhdGUgPSBzZWxmLmRmX3N0YXRlLnNyKCkKCiAgICBkZWYgc2F2ZV9hdWRpbyhzZWxmLCBhdWRpbzogbnAubmRhcnJheSwgdGFyZ2V0X3BhdGg6IFBhdGgpOgogICAgICAgIHRyeToKICAgICAgICAgICAgZnJvbSBkZi5lbmhhbmNlIGltcG9ydCBzYXZlX2F1ZGlvCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yIGFzIGU6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKCJQbGVhc2UgaW5zdGFsbCBkZWVwZmlsdGVybmV0IHBhY2thZ2UiKSBmcm9tIGUKICAgICAgICBzYXZlX2F1ZGlvKAogICAgICAgICAgICBmaWxlPXRhcmdldF9wYXRoLm5hbWUsCiAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICBzcj1zZWxmLnNhbXBsZV9yYXRlLAogICAgICAgICAgICBvdXRwdXRfZGlyPXN0cihzZWxmLnRhcmdldF9kaXJlY3RvcnkpLAogICAgICAgICkKCiAgICBkZWYgbG9hZF9hdWRpbyhzZWxmLCBmaWxlOiBzdHIpIC0+IHRvcmNoLlRlbnNvcjoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGZyb20gZGYuZW5oYW5jZSBpbXBvcnQgbG9hZF9hdWRpbwogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvciBhcyBlOgogICAgICAgICAgICByYWlzZSBJbXBvcnRFcnJvcigiUGxlYXNlIGluc3RhbGwgZGVlcGZpbHRlcm5ldCBwYWNrYWdlIikgZnJvbSBlCiAgICAgICAgYXVkaW8sIF8gPSBsb2FkX2F1ZGlvKGZpbGU9ZmlsZSwgc3I9c2VsZi5zYW1wbGVfcmF0ZSwgKipzZWxmLmt3YXJncykKICAgICAgICByZXR1cm4gYXVkaW8KCiAgICBkZWYgY2xlYW5fYXVkaW8oc2VsZiwgZGF0YTogdG9yY2guVGVuc29yKSAtPiB0b3JjaC5UZW5zb3I6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBmcm9tIGRmLmVuaGFuY2UgaW1wb3J0IGVuaGFuY2UKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3IgYXMgZToKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoIlBsZWFzZSBpbnN0YWxsIGRlZXBmaWx0ZXJuZXQgcGFja2FnZSIpIGZyb20gZQogICAgICAgIHJldHVybiBlbmhhbmNlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBkZl9zdGF0ZT1zZWxmLmRmX3N0YXRlLAogICAgICAgICAgICBhdWRpbz1kYXRhLAogICAgICAgICAgICBwYWQ9c2VsZi5wYWQsCiAgICAgICAgICAgIGF0dGVuX2xpbV9kYj1zZWxmLmF0dGVuX2xpbV9kYiwKICAgICAgICApCgoKZGVmIF9tdWx0aXByb2Nlc3NpbmdfY29tcGxldGVfdGFza3MoCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIHRhc2tzX3F1ZXVlOiBRdWV1ZSwKICAgIHJlc3VsdHNfcXVldWU6IFF1ZXVlLAopOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgICAgICAgICAgIEEgcXVldWUgdG8gZ2V0IHRoZSB0YXNrcyBmcm9tLgogICAgOnBhcmFtIHJlc3VsdHNfcXVldWU6ICAgICAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIHRoZSByZWR1Y2Ugbm9pc2Ugb2JqZWN0CiAgICBub2lzZV9yZWR1Y2VyID0gbm9pc2VfcmVkdWNlX3R5cGUoKipub2lzZV9yZWR1Y2VfYXJndW1lbnRzKQoKICAgICMgU3RhcnQgbGlzdGVuaW5nIHRvIHRoZSB0YXNrcyBxdWV1ZToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGF1ZGlvX2ZpbGU6CiAgICAgICAgYXVkaW9fZmlsZSA9IHRhc2tzX3F1ZXVlLmdldCgpCiAgICAgICAgaWYgYXVkaW9fZmlsZSA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgYnJlYWsKICAgICAgICBhdWRpb19maWxlID0gUGF0aChhdWRpb19maWxlKQogICAgICAgICMgQXBwbHkgbm9pc2UgcmVkdWN0aW9uIGFuZCBjb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQobm9pc2VfcmVkdWNlci5yZWR1Y2Vfbm9pc2UoYXVkaW9fZmlsZT1hdWRpb19maWxlKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgpkZWYgcmVkdWNlX25vaXNlX2RmbigKICAgIGF1ZGlvX3NvdXJjZTogc3RyLAogICAgdGFyZ2V0X2RpcmVjdG9yeTogc3RyLAogICAgcGFkOiBib29sID0gVHJ1ZSwKICAgIGF0dGVuX2xpbV9kYjogaW50ID0gTm9uZSwKICAgIHNpbGVuY2VfdGhyZXNob2xkOiBmbG9hdCA9IE5vbmUsCiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IFRydWUsCiAgICAqKmt3YXJncywKKToKICAgICIiIgogICAgUmVkdWNlIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMgdXNpbmcgRGVlcEZpbHRlck5ldC4KICAgIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS9SaWtvcm9zZS9EZWVwRmlsdGVyTmV0CiAgICBOb3RpY2UgdGhhdCB0aGUgc2F2ZWQgZmlsZXMgYXJlIGluIHdhdiBmb3JtYXQsIGV2ZW4gaWYgdGhlIG9yaWdpbmFsIGZpbGVzIGFyZSBpbiBvdGhlciBmb3JtYXQuCgogICAgOnBhcmFtIGF1ZGlvX3NvdXJjZTogICAgICAgIHBhdGggdG8gYXVkaW8gZmlsZSBvciBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIHRhcmdldCBkaXJlY3RvcnkgdG8gc2F2ZSBjbGVhbmVkIGF1ZGlvIGZpbGVzCiAgICA6cGFyYW0gcGFkOiAgICAgICAgICAgICAgICAgd2hldGhlciB0byBwYWQgdGhlIGF1ZGlvIGZpbGUgd2l0aCB6ZXJvcyBiZWZvcmUgY2xlYW5pbmcKICAgIDpwYXJhbSBhdHRlbl9saW1fZGI6ICAgICAgICBtYXhpbXVtIGF0dGVudWF0aW9uIGluIGRCCiAgICA6cGFyYW0gc2lsZW5jZV90aHJlc2hvbGQ6ICAgdGhlIHRocmVzaG9sZCB0byByZW1vdmUgc2lsZW5jZSBmcm9tIHRoZSBhdWRpbywgaW4gZEIuIElmIE5vbmUsIG5vIHNpbGVuY2UgcmVtb3ZhbCBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmZvcm1lZC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiBOdW1iZXIgb2YgcHJvY2Vzc2VzIHRvIHVzZSBmb3IgY2xlYW5pbmcgdGhlIGF1ZGlvIGZpbGVzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyBpcyB1c2VkLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgIHZlcmJvc2l0eSBsZXZlbC4gSWYgVHJ1ZSwgZGlzcGxheSBwcm9ncmVzcyBiYXIgYW5kIGxvZ3MuCiAgICA6cGFyYW0ga3dhcmdzOiAgICAgICAgICAgICAgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gcGFzcyB0byB0b3JjaGF1ZGlvLmxvYWQoKS4gRm9yIG1vcmUgaW5mb3JtYXRpb24gc2VlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGh0dHBzOi8vcHl0b3JjaC5vcmcvYXVkaW8vc3RhYmxlL2dlbmVyYXRlZC90b3JjaGF1ZGlvLmxvYWQuaHRtbAogICAgIiIiCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiUmVkdWNpbmcgbm9pc2UgZnJvbSBhdWRpbyBmaWxlcy4iKQoKICAgICMgY3JlYXRlIHRhcmdldCBkaXJlY3Rvcnk6CiAgICB0YXJnZXRfZGlyZWN0b3J5ID0gX2NyZWF0ZV90YXJnZXRfZGlyZWN0b3J5KHRhcmdldF9kaXJlY3RvcnkpCgogICAgIyBnZXQgYXVkaW8gZmlsZXM6CiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoYXVkaW9fc291cmNlKQoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJwYWQiOiBwYWQsCiAgICAgICAgImF0dGVuX2xpbV9kYiI6IGF0dGVuX2xpbV9kYiwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgICAgICAqKmt3YXJncywKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1ERk4sCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcsCiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLAogICAgICAgICAgICBkZXNjcmlwdGlvbj0iTm9pc2UtcmVkdWN0aW9uIiwKICAgICAgICAgICAgdmVyYm9zZT12ZXJib3NlLAogICAgICAgICkKICAgIGVsc2U6CiAgICAgICAgcmVzdWx0cyA9IF9ydW4oCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV90eXBlPURGTiwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgZGVzY3JpcHRpb249Ik5vaXNlLXJlZHVjdGlvbiIsCiAgICAgICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICAgICApCgogICAgcmV0dXJuIF9wcm9jZXNzX3Jlc3VsdHMocmVzdWx0cywgdmVyYm9zZSkKCgpkZWYgcmVkdWNlX25vaXNlKAogICAgYXVkaW9fc291cmNlOiBzdHIsCiAgICB0YXJnZXRfZGlyZWN0b3J5OiBzdHIsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBkdXJhdGlvbjogaW50ID0gTm9uZSwKICAgIGNoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzaWxlbmNlX3RocmVzaG9sZDogZmxvYXQgPSBOb25lLAogICAgdXNlX211bHRpcHJvY2Vzc2luZzogaW50ID0gMCwKICAgIHZlcmJvc2U6IGJvb2wgPSBUcnVlLAopOgogICAgIiIiCiAgICBSZWR1Y2Ugbm9pc2UgZnJvbSBhdWRpbyBmaWxlIG9yIGRpcmVjdG9yeSBjb250YWluaW5nIGF1ZGlvIGZpbGVzLgogICAgVGhlIGF1ZGlvIGZpbGVzIG11c3QgYmUgaW4gLndhdiBmb3JtYXQuCiAgICBUaGUgY2xlYW5lZCBhdWRpbyBmaWxlcyB3aWxsIGJlIHNhdmVkIGluIHRoZSB0YXJnZXRfZGlyZWN0b3J5LgogICAgRm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2lzZSByZWR1Y3Rpb24gYWxnb3JpdGhtIHNlZToKICAgIGh0dHBzOi8vZ2l0aHViLmNvbS90aW1zYWluYi9ub2lzZXJlZHVjZQogICAgTm90aWNlIHRoYXQgdGhlIHNhdmVkIGZpbGVzIGFyZSBpbiB3YXYgZm9ybWF0LCBldmVuIGlmIHRoZSBvcmlnaW5hbCBmaWxlcyBhcmUgaW4gb3RoZXIgZm9ybWF0LgoKICAgIDpwYXJhbSBhdWRpb19zb3VyY2U6ICAgICAgICBwYXRoIHRvIGF1ZGlvIGZpbGUgb3IgZGlyZWN0b3J5IGNvbnRhaW5pbmcgYXVkaW8gZmlsZXMKICAgIDpwYXJhbSB0YXJnZXRfZGlyZWN0b3J5OiAgICBwYXRoIHRvIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBjbGVhbmVkIGF1ZGlvIGZpbGVzLgogICAgOnBhcmFtIHNhbXBsZV9yYXRlOiAgICAgICAgIE51bWJlciBvZiBzYW1wbGVzIGluIG9uZSBzZWNvbmQgaW4gdGhlIGF1ZGlvIGZpbGUuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFzcyBgTm9uZWAgdG8ga2VlcCB0aGUgb3JpZ2luYWwgc2FtcGxlIHJhdGUuCiAgICA6cGFyYW0gZHVyYXRpb246ICAgICAgICAgICAgRHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgdG8gY2xlYW4gaW4gc2Vjb25kcy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXNzIGBOb25lYCB0byBrZWVwIHRoZSBvcmlnaW5hbCBkdXJhdGlvbi4KICAgIDpwYXJhbSBjaGFubmVsOiAgICAgICAgICAgICBDaGFubmVsIHRvIGNsZWFuLiBQYXNzIHRoZSBudW1iZXIgb2YgdGhlIGNoYW5uZWwgdG8gY2xlYW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gY2xlYW4gYWxsIGNoYW5uZWxzIHBhc3MgTm9uZS4KICAgIDpwYXJhbSBzaWxlbmNlX3RocmVzaG9sZDogICBUaGUgdGhyZXNob2xkIHRvIHJlbW92ZSBzaWxlbmNlIGZyb20gdGhlIGF1ZGlvLCBpbiBkQi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCBubyBzaWxlbmNlIHJlbW92YWwgaXMgcGVyZm9ybWVkLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6IE51bWJlciBvZiBwcm9jZXNzZXMgdG8gdXNlIGZvciBjbGVhbmluZyB0aGUgYXVkaW8gZmlsZXMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgMCwgbm8gbXVsdGlwcm9jZXNzaW5nIGlzIHVzZWQuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgVmVyYm9zaXR5IGxldmVsLiBJZiBUcnVlLCBkaXNwbGF5IHByb2dyZXNzIGJhci4KICAgICIiIgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlJlZHVjaW5nIG5vaXNlIGZyb20gYXVkaW8gZmlsZXMuIikKCiAgICAjIGNyZWF0ZSB0YXJnZXQgZGlyZWN0b3J5OgogICAgdGFyZ2V0X2RpcmVjdG9yeSA9IF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5KQoKICAgICMgZ2V0IGF1ZGlvIGZpbGVzOgogICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGF1ZGlvX3NvdXJjZSkKCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHMgPSB7CiAgICAgICAgInRhcmdldF9kaXJlY3RvcnkiOiB0YXJnZXRfZGlyZWN0b3J5LAogICAgICAgICJzYW1wbGVfcmF0ZSI6IHNhbXBsZV9yYXRlLAogICAgICAgICJkdXJhdGlvbiI6IGR1cmF0aW9uLAogICAgICAgICJjaGFubmVsIjogY2hhbm5lbCwKICAgICAgICAic2lsZW5jZV90aHJlc2hvbGQiOiBzaWxlbmNlX3RocmVzaG9sZCwKICAgIH0KCiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBub2lzZV9yZWR1Y2VfdHlwZT1SZWR1Y2VOb2lzZSwKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50cz1ub2lzZV9yZWR1Y2VfYXJndW1lbnRzLAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgbm9pc2VfcmVkdWNlX3R5cGU9UmVkdWNlTm9pc2UsCiAgICAgICAgICAgIG5vaXNlX3JlZHVjZV9hcmd1bWVudHM9bm9pc2VfcmVkdWNlX2FyZ3VtZW50cywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJOb2lzZS1yZWR1Y3Rpb24iLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHMsIHZlcmJvc2UpCgoKZGVmIF9jcmVhdGVfdGFyZ2V0X2RpcmVjdG9yeSh0YXJnZXRfZGlyZWN0b3J5OiBzdHIpIC0+IHN0cjoKICAgIHRhcmdldF9kaXJlY3RvcnkgPSBQYXRoKHRhcmdldF9kaXJlY3RvcnkpCiAgICBpZiBub3QgdGFyZ2V0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICB0YXJnZXRfZGlyZWN0b3J5Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgIHJldHVybiBzdHIodGFyZ2V0X2RpcmVjdG9yeSkKCgpkZWYgX2dldF9hdWRpb19maWxlcyhhdWRpb19zb3VyY2U6IHN0cik6CiAgICBhdWRpb19zb3VyY2UgPSBQYXRoKGF1ZGlvX3NvdXJjZSkKICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgIGlmIGF1ZGlvX3NvdXJjZS5pc19kaXIoKToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoYXVkaW9fc291cmNlLmdsb2IoIiouKiIpKQogICAgZWxpZiBhdWRpb19zb3VyY2UuaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzLmFwcGVuZChhdWRpb19zb3VyY2UpCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiYXVkaW9fc291cmNlIG11c3QgYmUgYSBmaWxlIG9yIGEgZGlyZWN0b3J5LCBnb3Qge2F1ZGlvX3NvdXJjZX0iCiAgICAgICAgKQogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9wYXJhbGxlbF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIG5fd29ya2VyczogaW50LAogICAgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sCiAgICBkZXNjcmlwdGlvbjogc3RyLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgIiIiCiAgICBSdW4gbXVsdGlwbGUgbm9pc2UgcmVkdWNlIHdvcmtlcnMgd2l0aCBtdWx0aXByb2Nlc3NpbmcgdG8gY29tcGxldGUgdGhlIHRhc2tzIHRoYXQgd2lsbCBiZSBjcmVhdGVkIG9uIHRoZSBwcm92aWRlZAogICAgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgVGhlIG5vaXNlIHJlZHVjZSB0eXBlIHRvIHVzZS4KICAgIDpwYXJhbSBuX3dvcmtlcnM6ICAgICAgICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlLgogICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBDaGVjayB0aGUgbnVtYmVyIG9mIHdvcmtlcnM6CiAgICBpZiBuX3dvcmtlcnMgPiBsZW4oYXVkaW9fZmlsZXMpOgogICAgICAgIF9MT0dHRVIud2FybmluZygKICAgICAgICAgICAgZiJUaGUgbnVtYmVyIG9mIHdvcmtlcnMgKHtuX3dvcmtlcnN9KSBpcyBsYXJnZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGF1ZGlvIGZpbGVzICh7bGVuKGF1ZGlvX2ZpbGVzKX0pLiAiCiAgICAgICAgICAgIGYiU2V0dGluZyB0aGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8ge2xlbihhdWRpb19maWxlcyl9LiIKICAgICAgICApCiAgICAgICAgbl93b3JrZXJzID0gbGVuKGF1ZGlvX2ZpbGVzKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlczoKICAgIHRhc2tzX3F1ZXVlID0gUXVldWUoKQogICAgcmVzdWx0c19xdWV1ZSA9IFF1ZXVlKCkKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzID0gWwogICAgICAgIFByb2Nlc3MoCiAgICAgICAgICAgIHRhcmdldD1fbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzLAogICAgICAgICAgICBrd2FyZ3M9ewogICAgICAgICAgICAgICAgIm5vaXNlX3JlZHVjZV90eXBlIjogbm9pc2VfcmVkdWNlX3R5cGUsCiAgICAgICAgICAgICAgICAibm9pc2VfcmVkdWNlX2FyZ3VtZW50cyI6IG5vaXNlX3JlZHVjZV9hcmd1bWVudHMsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAjIHRhc2tzX3F1ZXVlLnB1dCh0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKS50b190dXBsZSgpKQogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChhdWRpb19maWxlKQoKICAgICMgUHV0IHRoZSBzdG9wIG1hcmtzIGluIHRoZSBxdWV1ZToKICAgIGZvciBfIGluIHJhbmdlKG5fd29ya2Vycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0czoKICAgIHJlc3VsdHMgPSBbXQogICAgc3RvcF9tYXJrc19jb3VudGVyID0gMAogICAgd2l0aCB0cWRtKAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKSBhcyBwcm9ncmVzc2JhcjoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAjIEdldCBhIHJlc3VsdCBmcm9tIHRoZSBxdWV1ZToKICAgICAgICAgICAgcmVzdWx0OiBUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dID0gcmVzdWx0c19xdWV1ZS5nZXQoKQogICAgICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgICAgICBzdG9wX21hcmtzX2NvdW50ZXIgKz0gMQogICAgICAgICAgICAgICAgaWYgc3RvcF9tYXJrc19jb3VudGVyID09IG5fd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCiAgICAgICAgICAgICAgICBwcm9ncmVzc2Jhci51cGRhdGUoMSkKCiAgICAjIFdhaXQgZm9yIHRoZSBwcm9jZXNzZXMgdG8gZmluaXNoOgogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9ydW4oCiAgICBub2lzZV9yZWR1Y2VfdHlwZTogVHlwZVtSZWR1Y2VOb2lzZUJhc2VdLAogICAgbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogZGljdCwKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgZGVzY3JpcHRpb246IHN0ciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSBub2lzZSByZWR1Y2UgYWxnb3JpdGhtIG9uIHRoZSBnaXZlbiBhdWRpbyBmaWxlcyBhbmQgY29sbGVjdCB0aGUgcmVzdWx0cy4KCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX3R5cGU6ICAgICAgIFRoZSBub2lzZSByZWR1Y2UgdHlwZSB0byB1c2UuCiAgICA6cGFyYW0gbm9pc2VfcmVkdWNlX2FyZ3VtZW50czogIFRoZSBub2lzZXJlZHVjZSBpbml0aWFsaXphdGlvbiBrd2FyZ3MuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICAgICAgICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICAgICAgICAgIFRoZSBkZXNjcmlwdGlvbiB0byB1c2UgZm9yIHRoZSBwcm9ncmVzcyBiYXIuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIENyZWF0ZSB0aGUgcmVkdWNlIG5vaXNlIG9iamVjdDoKICAgIG5vaXNlX3JlZHVjZXIgPSBub2lzZV9yZWR1Y2VfdHlwZSgqKm5vaXNlX3JlZHVjZV9hcmd1bWVudHMpCgogICAgIyBSdW4gdGhlIG5vaXNlIHJlZHVjZSBhbGdvcml0aG0gb24gdGhlIGF1ZGlvIGZpbGVzIGFuZCBjb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBmb3IgYXVkaW9fZmlsZSBpbiB0cWRtKAogICAgICAgIGF1ZGlvX2ZpbGVzLAogICAgICAgIGRlc2M9ZGVzY3JpcHRpb24sCiAgICAgICAgdW5pdD0iZmlsZSIsCiAgICAgICAgdG90YWw9bGVuKGF1ZGlvX2ZpbGVzKSwKICAgICAgICBkaXNhYmxlPW5vdCB2ZXJib3NlLAogICAgKToKICAgICAgICByZXN1bHRzLmFwcGVuZChub2lzZV9yZWR1Y2VyLnJlZHVjZV9ub2lzZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpKQoKICAgIHJldHVybiByZXN1bHRzCgoKZGVmIF9wcm9jZXNzX3Jlc3VsdHMoCiAgICByZXN1bHRzOiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
+    origin_filename: ''
   description: Reduce noise from audio files
+  command: ''
+  image: ''
   default_handler: reduce_noise
   disable_auto_mount: false
-  clone_target_dir: ''
-  env: []
-  priority_class_name: ''
-  preemption_mode: prevent
-  affinity: null
-  tolerations: null
-  security_context: {}
+metadata:
+  name: noise-reduction
+  tag: ''
+  categories:
+  - data-preparation
+  - audio
+kind: job
 verbose: false
 
         
diff --git a/functions/master/noise_reduction/latest/static/item.html b/functions/master/noise_reduction/latest/static/item.html
index df39e08a..63a2e019 100644
--- a/functions/master/noise_reduction/latest/static/item.html
+++ b/functions/master/noise_reduction/latest/static/item.html
@@ -31,7 +31,7 @@
 apiVersion: v1
 categories:
   - data-preparation
-  - machine-learning
+  - audio
 description: Reduce noise from audio files
 doc: ''
 example: noise_reduction.ipynb
@@ -41,7 +41,7 @@
 labels:
   author: yonatans
 maintainers: []
-mlrunVersion: 1.5.2
+mlrunVersion: 1.7.0
 name: noise-reduction
 platformVersion: 3.5.3
 spec:
@@ -56,7 +56,7 @@
     torchaudio>=2.1.2,
   ]
 url: ''
-version: 1.0.0
+version: 1.1.0
         
     
diff --git a/functions/master/noise_reduction/latest/static/noise_reduction.html b/functions/master/noise_reduction/latest/static/noise_reduction.html index dd131944..cfbc45de 100644 --- a/functions/master/noise_reduction/latest/static/noise_reduction.html +++ b/functions/master/noise_reduction/latest/static/noise_reduction.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/1.3.0/src/function.yaml b/functions/master/onnx_utils/1.3.0/src/function.yaml index 67ebf0d5..af3d5082 100644 --- a/functions/master/onnx_utils/1.3.0/src/function.yaml +++ b/functions/master/onnx_utils/1.3.0/src/function.yaml @@ -1,17 +1,40 @@ +kind: job +metadata: + categories: + - utils + - deep-learning + name: onnx-utils + tag: '' +verbose: false spec: - allow_empty_resources: true + build: + code_origin: '' + base_image: mlrun/mlrun + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg== + requirements: + - tqdm~=4.67.1 + - tensorflow~=2.19.0 + - tf_keras~=2.19.0 + - torch~=2.6.0 + - torchvision~=0.21.0 + - onnx~=1.17.0 + - onnxruntime~=1.19.2 + - onnxoptimizer~=0.3.13 + - onnxmltools~=1.13.0 + - tf2onnx~=1.16.1 + - plotly~=5.4.0 + with_mlrun: false + auto_build: true + disable_auto_mount: false description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. - default_handler: to_onnx - command: '' image: '' entry_points: tf_keras_to_onnx: - has_kwargs: false - lineno: 26 - name: tf_keras_to_onnx doc: Convert a TF.Keras model to an ONNX model and log it back to MLRun as a new model object. + name: tf_keras_to_onnx parameters: - name: model_handler doc: An initialized TFKerasModelHandler with a loaded model to convert to @@ -36,12 +59,12 @@ spec: will be tried to be read from the model artifact. Defaulted to None.' default: null has_varargs: false - pytorch_to_onnx: has_kwargs: false - lineno: 81 - name: pytorch_to_onnx + lineno: 26 + pytorch_to_onnx: doc: Convert a PyTorch model to an ONNX model and log it back to MLRun as a new model object. + name: pytorch_to_onnx parameters: - name: model_handler doc: An initialized PyTorchModelHandler with a loaded model to convert to @@ -94,11 +117,11 @@ spec: output layer. Defaulted to True. Will be ignored if 'dynamic_axes' is provided. default: true has_varargs: false - to_onnx: has_kwargs: false - lineno: 160 - name: to_onnx + lineno: 81 + to_onnx: doc: Convert the given model to an ONNX model. + name: to_onnx parameters: - name: context type: MLClientCtx @@ -128,11 +151,11 @@ spec: "help". default: null has_varargs: false - optimize: has_kwargs: false - lineno: 224 - name: optimize + lineno: 160 + optimize: doc: Optimize the given ONNX model. + name: optimize parameters: - name: context type: MLClientCtx @@ -159,30 +182,8 @@ spec: overridden. Defaulted to None. default: null has_varargs: false - build: - code_origin: '' - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg== - auto_build: true - base_image: mlrun/mlrun - with_mlrun: false - requirements: - - tqdm~=4.67.1 - - tensorflow~=2.19.0 - - tf_keras~=2.19.0 - - torch~=2.6.0 - - torchvision~=0.21.0 - - onnx~=1.17.0 - - onnxruntime~=1.19.2 - - onnxoptimizer~=0.3.13 - - onnxmltools~=1.13.0 - - tf2onnx~=1.16.1 - - plotly~=5.4.0 - origin_filename: '' - disable_auto_mount: false -metadata: - categories: - - utils - tag: '' - name: onnx-utils -verbose: false -kind: job + has_kwargs: false + lineno: 224 + default_handler: to_onnx + allow_empty_resources: true + command: '' diff --git a/functions/master/onnx_utils/1.3.0/src/item.yaml b/functions/master/onnx_utils/1.3.0/src/item.yaml index 4a9455d9..ba65ce91 100644 --- a/functions/master/onnx_utils/1.3.0/src/item.yaml +++ b/functions/master/onnx_utils/1.3.0/src/item.yaml @@ -1,6 +1,7 @@ apiVersion: v1 categories: - utils +- deep-learning description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. doc: '' diff --git a/functions/master/onnx_utils/1.3.0/static/documentation.html b/functions/master/onnx_utils/1.3.0/static/documentation.html index 5f1eec32..7505c831 100644 --- a/functions/master/onnx_utils/1.3.0/static/documentation.html +++ b/functions/master/onnx_utils/1.3.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/1.3.0/static/example.html b/functions/master/onnx_utils/1.3.0/static/example.html index 7eff856a..3d4d124b 100644 --- a/functions/master/onnx_utils/1.3.0/static/example.html +++ b/functions/master/onnx_utils/1.3.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/1.3.0/static/function.html b/functions/master/onnx_utils/1.3.0/static/function.html index 584bb31e..2d049e5f 100644 --- a/functions/master/onnx_utils/1.3.0/static/function.html +++ b/functions/master/onnx_utils/1.3.0/static/function.html @@ -28,20 +28,43 @@
         
+kind: job
+metadata:
+  categories:
+  - utils
+  - deep-learning
+  name: onnx-utils
+  tag: ''
+verbose: false
 spec:
-  allow_empty_resources: true
+  build:
+    code_origin: ''
+    base_image: mlrun/mlrun
+    origin_filename: ''
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg==
+    requirements:
+    - tqdm~=4.67.1
+    - tensorflow~=2.19.0
+    - tf_keras~=2.19.0
+    - torch~=2.6.0
+    - torchvision~=0.21.0
+    - onnx~=1.17.0
+    - onnxruntime~=1.19.2
+    - onnxoptimizer~=0.3.13
+    - onnxmltools~=1.13.0
+    - tf2onnx~=1.16.1
+    - plotly~=5.4.0
+    with_mlrun: false
+    auto_build: true
+  disable_auto_mount: false
   description: ONNX intigration in MLRun, some utils functions for the ONNX framework,
     optimizing and converting models from different framework to ONNX using MLRun.
-  default_handler: to_onnx
-  command: ''
   image: ''
   entry_points:
     tf_keras_to_onnx:
-      has_kwargs: false
-      lineno: 26
-      name: tf_keras_to_onnx
       doc: Convert a TF.Keras model to an ONNX model and log it back to MLRun as a
         new model object.
+      name: tf_keras_to_onnx
       parameters:
       - name: model_handler
         doc: An initialized TFKerasModelHandler with a loaded model to convert to
@@ -66,12 +89,12 @@
           will be tried to be read from the model artifact. Defaulted to None.'
         default: null
       has_varargs: false
-    pytorch_to_onnx:
       has_kwargs: false
-      lineno: 81
-      name: pytorch_to_onnx
+      lineno: 26
+    pytorch_to_onnx:
       doc: Convert a PyTorch model to an ONNX model and log it back to MLRun as a
         new model object.
+      name: pytorch_to_onnx
       parameters:
       - name: model_handler
         doc: An initialized PyTorchModelHandler with a loaded model to convert to
@@ -124,11 +147,11 @@
           output layer. Defaulted to True. Will be ignored if 'dynamic_axes' is provided.
         default: true
       has_varargs: false
-    to_onnx:
       has_kwargs: false
-      lineno: 160
-      name: to_onnx
+      lineno: 81
+    to_onnx:
       doc: Convert the given model to an ONNX model.
+      name: to_onnx
       parameters:
       - name: context
         type: MLClientCtx
@@ -158,11 +181,11 @@
           "help".
         default: null
       has_varargs: false
-    optimize:
       has_kwargs: false
-      lineno: 224
-      name: optimize
+      lineno: 160
+    optimize:
       doc: Optimize the given ONNX model.
+      name: optimize
       parameters:
       - name: context
         type: MLClientCtx
@@ -189,33 +212,11 @@
           overridden. Defaulted to None.
         default: null
       has_varargs: false
-  build:
-    code_origin: ''
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg==
-    auto_build: true
-    base_image: mlrun/mlrun
-    with_mlrun: false
-    requirements:
-    - tqdm~=4.67.1
-    - tensorflow~=2.19.0
-    - tf_keras~=2.19.0
-    - torch~=2.6.0
-    - torchvision~=0.21.0
-    - onnx~=1.17.0
-    - onnxruntime~=1.19.2
-    - onnxoptimizer~=0.3.13
-    - onnxmltools~=1.13.0
-    - tf2onnx~=1.16.1
-    - plotly~=5.4.0
-    origin_filename: ''
-  disable_auto_mount: false
-metadata:
-  categories:
-  - utils
-  tag: ''
-  name: onnx-utils
-verbose: false
-kind: job
+      has_kwargs: false
+      lineno: 224
+  default_handler: to_onnx
+  allow_empty_resources: true
+  command: ''
 
         
     
diff --git a/functions/master/onnx_utils/1.3.0/static/item.html b/functions/master/onnx_utils/1.3.0/static/item.html index ab6f22d2..b575e949 100644 --- a/functions/master/onnx_utils/1.3.0/static/item.html +++ b/functions/master/onnx_utils/1.3.0/static/item.html @@ -31,6 +31,7 @@ apiVersion: v1 categories: - utils +- deep-learning description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. doc: '' diff --git a/functions/master/onnx_utils/1.3.0/static/onnx_utils.html b/functions/master/onnx_utils/1.3.0/static/onnx_utils.html index c1964206..297beedb 100644 --- a/functions/master/onnx_utils/1.3.0/static/onnx_utils.html +++ b/functions/master/onnx_utils/1.3.0/static/onnx_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/latest/src/function.yaml b/functions/master/onnx_utils/latest/src/function.yaml index 67ebf0d5..af3d5082 100644 --- a/functions/master/onnx_utils/latest/src/function.yaml +++ b/functions/master/onnx_utils/latest/src/function.yaml @@ -1,17 +1,40 @@ +kind: job +metadata: + categories: + - utils + - deep-learning + name: onnx-utils + tag: '' +verbose: false spec: - allow_empty_resources: true + build: + code_origin: '' + base_image: mlrun/mlrun + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg== + requirements: + - tqdm~=4.67.1 + - tensorflow~=2.19.0 + - tf_keras~=2.19.0 + - torch~=2.6.0 + - torchvision~=0.21.0 + - onnx~=1.17.0 + - onnxruntime~=1.19.2 + - onnxoptimizer~=0.3.13 + - onnxmltools~=1.13.0 + - tf2onnx~=1.16.1 + - plotly~=5.4.0 + with_mlrun: false + auto_build: true + disable_auto_mount: false description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. - default_handler: to_onnx - command: '' image: '' entry_points: tf_keras_to_onnx: - has_kwargs: false - lineno: 26 - name: tf_keras_to_onnx doc: Convert a TF.Keras model to an ONNX model and log it back to MLRun as a new model object. + name: tf_keras_to_onnx parameters: - name: model_handler doc: An initialized TFKerasModelHandler with a loaded model to convert to @@ -36,12 +59,12 @@ spec: will be tried to be read from the model artifact. Defaulted to None.' default: null has_varargs: false - pytorch_to_onnx: has_kwargs: false - lineno: 81 - name: pytorch_to_onnx + lineno: 26 + pytorch_to_onnx: doc: Convert a PyTorch model to an ONNX model and log it back to MLRun as a new model object. + name: pytorch_to_onnx parameters: - name: model_handler doc: An initialized PyTorchModelHandler with a loaded model to convert to @@ -94,11 +117,11 @@ spec: output layer. Defaulted to True. Will be ignored if 'dynamic_axes' is provided. default: true has_varargs: false - to_onnx: has_kwargs: false - lineno: 160 - name: to_onnx + lineno: 81 + to_onnx: doc: Convert the given model to an ONNX model. + name: to_onnx parameters: - name: context type: MLClientCtx @@ -128,11 +151,11 @@ spec: "help". default: null has_varargs: false - optimize: has_kwargs: false - lineno: 224 - name: optimize + lineno: 160 + optimize: doc: Optimize the given ONNX model. + name: optimize parameters: - name: context type: MLClientCtx @@ -159,30 +182,8 @@ spec: overridden. Defaulted to None. default: null has_varargs: false - build: - code_origin: '' - functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg== - auto_build: true - base_image: mlrun/mlrun - with_mlrun: false - requirements: - - tqdm~=4.67.1 - - tensorflow~=2.19.0 - - tf_keras~=2.19.0 - - torch~=2.6.0 - - torchvision~=0.21.0 - - onnx~=1.17.0 - - onnxruntime~=1.19.2 - - onnxoptimizer~=0.3.13 - - onnxmltools~=1.13.0 - - tf2onnx~=1.16.1 - - plotly~=5.4.0 - origin_filename: '' - disable_auto_mount: false -metadata: - categories: - - utils - tag: '' - name: onnx-utils -verbose: false -kind: job + has_kwargs: false + lineno: 224 + default_handler: to_onnx + allow_empty_resources: true + command: '' diff --git a/functions/master/onnx_utils/latest/src/item.yaml b/functions/master/onnx_utils/latest/src/item.yaml index 4a9455d9..ba65ce91 100644 --- a/functions/master/onnx_utils/latest/src/item.yaml +++ b/functions/master/onnx_utils/latest/src/item.yaml @@ -1,6 +1,7 @@ apiVersion: v1 categories: - utils +- deep-learning description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. doc: '' diff --git a/functions/master/onnx_utils/latest/static/documentation.html b/functions/master/onnx_utils/latest/static/documentation.html index 5f1eec32..7505c831 100644 --- a/functions/master/onnx_utils/latest/static/documentation.html +++ b/functions/master/onnx_utils/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/latest/static/example.html b/functions/master/onnx_utils/latest/static/example.html index 7eff856a..3d4d124b 100644 --- a/functions/master/onnx_utils/latest/static/example.html +++ b/functions/master/onnx_utils/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/onnx_utils/latest/static/function.html b/functions/master/onnx_utils/latest/static/function.html index 584bb31e..2d049e5f 100644 --- a/functions/master/onnx_utils/latest/static/function.html +++ b/functions/master/onnx_utils/latest/static/function.html @@ -28,20 +28,43 @@
         
+kind: job
+metadata:
+  categories:
+  - utils
+  - deep-learning
+  name: onnx-utils
+  tag: ''
+verbose: false
 spec:
-  allow_empty_resources: true
+  build:
+    code_origin: ''
+    base_image: mlrun/mlrun
+    origin_filename: ''
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg==
+    requirements:
+    - tqdm~=4.67.1
+    - tensorflow~=2.19.0
+    - tf_keras~=2.19.0
+    - torch~=2.6.0
+    - torchvision~=0.21.0
+    - onnx~=1.17.0
+    - onnxruntime~=1.19.2
+    - onnxoptimizer~=0.3.13
+    - onnxmltools~=1.13.0
+    - tf2onnx~=1.16.1
+    - plotly~=5.4.0
+    with_mlrun: false
+    auto_build: true
+  disable_auto_mount: false
   description: ONNX intigration in MLRun, some utils functions for the ONNX framework,
     optimizing and converting models from different framework to ONNX using MLRun.
-  default_handler: to_onnx
-  command: ''
   image: ''
   entry_points:
     tf_keras_to_onnx:
-      has_kwargs: false
-      lineno: 26
-      name: tf_keras_to_onnx
       doc: Convert a TF.Keras model to an ONNX model and log it back to MLRun as a
         new model object.
+      name: tf_keras_to_onnx
       parameters:
       - name: model_handler
         doc: An initialized TFKerasModelHandler with a loaded model to convert to
@@ -66,12 +89,12 @@
           will be tried to be read from the model artifact. Defaulted to None.'
         default: null
       has_varargs: false
-    pytorch_to_onnx:
       has_kwargs: false
-      lineno: 81
-      name: pytorch_to_onnx
+      lineno: 26
+    pytorch_to_onnx:
       doc: Convert a PyTorch model to an ONNX model and log it back to MLRun as a
         new model object.
+      name: pytorch_to_onnx
       parameters:
       - name: model_handler
         doc: An initialized PyTorchModelHandler with a loaded model to convert to
@@ -124,11 +147,11 @@
           output layer. Defaulted to True. Will be ignored if 'dynamic_axes' is provided.
         default: true
       has_varargs: false
-    to_onnx:
       has_kwargs: false
-      lineno: 160
-      name: to_onnx
+      lineno: 81
+    to_onnx:
       doc: Convert the given model to an ONNX model.
+      name: to_onnx
       parameters:
       - name: context
         type: MLClientCtx
@@ -158,11 +181,11 @@
           "help".
         default: null
       has_varargs: false
-    optimize:
       has_kwargs: false
-      lineno: 224
-      name: optimize
+      lineno: 160
+    optimize:
       doc: Optimize the given ONNX model.
+      name: optimize
       parameters:
       - name: context
         type: MLClientCtx
@@ -189,33 +212,11 @@
           overridden. Defaulted to None.
         default: null
       has_varargs: false
-  build:
-    code_origin: ''
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAxOSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgQ2FsbGFibGUsIERpY3QsIExpc3QsIFR1cGxlCgppbXBvcnQgbWxydW4KCgpjbGFzcyBfVG9PTk5YQ29udmVyc2lvbnM6CiAgICAiIiIKICAgIEFuIE9OTlggY29udmVyc2lvbiBmdW5jdGlvbnMgbGlicmFyeSBjbGFzcy4KICAgICIiIgoKICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiB0Zl9rZXJhc190b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50XSwgc3RyXV0gPSBOb25lLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBDb252ZXJ0IGEgVEYuS2VyYXMgbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICBBbiBpbml0aWFsaXplZCBURktlcmFzTW9kZWxIYW5kbGVyIHdpdGggYSBsb2FkZWQgbW9kZWwgdG8gY29udmVydCB0byBPTk5YLgogICAgICAgIDpwYXJhbSBvbm54X21vZGVsX25hbWU6IFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICBXaGV0aGVyIG9yIG5vdCB0byBvcHRpbWl6ZSB0aGUgT05OWCBtb2RlbCB1c2luZyAnb25ueG9wdGltaXplcicgYmVmb3JlIHNhdmluZyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdGVkIHRvIFRydWUuCiAgICAgICAgOnBhcmFtIGlucHV0X3NpZ25hdHVyZTogQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBhbiBpbnB1dCBsYXllciB0dXBsZS4gQW4gaW5wdXQgbGF5ZXIgdHVwbGUgaXMgYSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbMF0gPSBMYXllcidzIHNoYXBlLCBhIHR1cGxlIG9mIGludGVnZXJzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBOb25lLCB0aGUgaW5wdXQgc2lnbmF0dXJlIHdpbGwgYmUgdHJpZWQgdG8gYmUgcmVhZCBmcm9tIHRoZSBtb2RlbCBhcnRpZmFjdC4gRGVmYXVsdGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG8gTm9uZS4KICAgICAgICAiIiIKICAgICAgICAjIEltcG9ydCB0aGUgZnJhbWV3b3JrIGFuZCBoYW5kbGVyOgogICAgICAgIGltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCiAgICAgICAgZnJvbSBtbHJ1bi5mcmFtZXdvcmtzLnRmX2tlcmFzIGltcG9ydCBURktlcmFzVXRpbHMKCiAgICAgICAgIyBDaGVjayB0aGUgZ2l2ZW4gJ2lucHV0X3NpZ25hdHVyZScgcGFyYW1ldGVyOgogICAgICAgIGlmIGlucHV0X3NpZ25hdHVyZSBpcyBOb25lOgogICAgICAgICAgICAjIFJlYWQgdGhlIGlucHV0cyBmcm9tIHRoZSBtb2RlbDoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbW9kZWxfaGFuZGxlci5yZWFkX2lucHV0c19mcm9tX21vZGVsKCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1blJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSBwcm92aWRlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXIuIFRoZSBmdW5jdGlvbiB0cmllZCByZWFkaW5nIHRoZSBpbnB1dCBsYXllcnMgIgogICAgICAgICAgICAgICAgICAgIGYiaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSBidXQgZmFpbGVkIHdpdGggdGhlIGZvbGxvd2luZyBlcnJvcjoge2Vycm9yfSIKICAgICAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICAjIFBhcnNlIHRoZSAnaW5wdXRfc2lnbmF0dXJlJyBwYXJhbWV0ZXI6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IFsKICAgICAgICAgICAgICAgIHRmLlRlbnNvclNwZWMoCiAgICAgICAgICAgICAgICAgICAgc2hhcGU9c2hhcGUsCiAgICAgICAgICAgICAgICAgICAgZHR5cGU9VEZLZXJhc1V0aWxzLmNvbnZlcnRfdmFsdWVfdHlwZV90b190Zl9kdHlwZSgKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICBdCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICkKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgcHl0b3JjaF90b19vbm54KAogICAgICAgIG1vZGVsX2hhbmRsZXIsCiAgICAgICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgICAgIG9wdGltaXplX21vZGVsOiBib29sID0gVHJ1ZSwKICAgICAgICBpbnB1dF9zaWduYXR1cmU6IExpc3RbVHVwbGVbVHVwbGVbaW50LCAuLi5dLCBzdHJdXSA9IE5vbmUsCiAgICAgICAgaW5wdXRfbGF5ZXJzX25hbWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG91dHB1dF9sYXllcnNfbmFtZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICAgICAgZHluYW1pY19heGVzOiBEaWN0W3N0ciwgRGljdFtpbnQsIHN0cl1dID0gTm9uZSwKICAgICAgICBpc19iYXRjaGVkOiBib29sID0gVHJ1ZSwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBhIFB5VG9yY2ggbW9kZWwgdG8gYW4gT05OWCBtb2RlbCBhbmQgbG9nIGl0IGJhY2sgdG8gTUxSdW4gYXMgYSBuZXcgbW9kZWwgb2JqZWN0LgoKICAgICAgICA6cGFyYW0gbW9kZWxfaGFuZGxlcjogICAgICAgQW4gaW5pdGlhbGl6ZWQgUHlUb3JjaE1vZGVsSGFuZGxlciB3aXRoIGEgbG9hZGVkIG1vZGVsIHRvIGNvbnZlcnQgdG8gT05OWC4KICAgICAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgICAgVGhlIG5hbWUgdG8gdXNlIHRvIGxvZyB0aGUgY29udmVydGVkIE9OTlggbW9kZWwuIElmIG5vdCBnaXZlbiwgdGhlIGdpdmVuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtb2RlbF9uYW1lYCB3aWxsIGJlIHVzZWQgd2l0aCBhbiBhZGRpdGlvbmFsIHN1ZmZpeCBgX29ubnhgLiBEZWZhdWx0ZWQgdG8gTm9uZS4KICAgICAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgICAgV2hldGhlciBvciBub3QgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsLiBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgICAgICA6cGFyYW0gaW5wdXRfc2lnbmF0dXJlOiAgICAgQSBsaXN0IG9mIHRoZSBpbnB1dCBsYXllcnMgc2hhcGUgYW5kIGRhdGEgdHlwZSBwcm9wZXJ0aWVzLiBFeHBlY3RlZCB0byByZWNlaXZlIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYW4gaW5wdXQgbGF5ZXIgdHVwbGUuIEFuIGlucHV0IGxheWVyIHR1cGxlIGlzIGEgdHVwbGUgb2Y6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFswXSA9IExheWVyJ3Mgc2hhcGUsIGEgdHVwbGUgb2YgaW50ZWdlcnMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsxXSA9IExheWVyJ3MgZGF0YSB0eXBlLCBhIG1scnVuLmRhdGFfdHlwZXMuVmFsdWVUeXBlIHN0cmluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgdGhlIGlucHV0IHNpZ25hdHVyZSB3aWxsIGJlIHRyaWVkIHRvIGJlIHJlYWQgZnJvbSB0aGUgbW9kZWwgYXJ0aWZhY3QuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpbnB1dF9sYXllcnNfbmFtZXM6ICBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgaW5wdXQgbm9kZXMgb2YgdGhlIGdyYXBoIGluIG9yZGVyLiBBbGwgb2YgdGhlIG90aGVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcnMgKGlubmVyIGxheWVycykgY2FuIGJlIHNldCBhcyB3ZWxsIGJ5IHBhc3NpbmcgYWRkaXRpb25hbCBuYW1lcyBpbiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdC4gVGhlIG9yZGVyIGlzIGJ5IHRoZSBvcmRlciBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwuIElmIE5vbmUsIHRoZSBpbnB1dHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBpbnB1dHMuIElmIGl0cyBhbHNvIE5vbmUsIGl0IGlzIGRlZmF1bHRlZCB0bzoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlucHV0XzAiLCAiaW5wdXRfMSIsIC4uLgogICAgICAgIDpwYXJhbSBvdXRwdXRfbGF5ZXJzX25hbWVzOiBMaXN0IG9mIG5hbWVzIHRvIGFzc2lnbiB0byB0aGUgb3V0cHV0IG5vZGVzIG9mIHRoZSBncmFwaCBpbiBvcmRlci4gSWYgTm9uZSwgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dHMgd2lsbCBiZSByZWFkIGZyb20gdGhlIGhhbmRsZXIncyBvdXRwdXRzLiBJZiBpdHMgYWxzbyBOb25lLCBpdCBpcyBkZWZhdWx0ZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG86ICJvdXRwdXRfMCIgKGZvciBtdWx0aXBsZSBvdXRwdXRzLCB0aGlzIHBhcmFtZXRlciBtdXN0IGJlIHByb3ZpZGVkKS4KICAgICAgICA6cGFyYW0gZHluYW1pY19heGVzOiAgICAgICAgSWYgcGFydCBvZiB0aGUgaW5wdXQgLyBvdXRwdXQgc2hhcGUgaXMgZHluYW1pYywgbGlrZSAoYmF0Y2hfc2l6ZSwgMywgMzIsIDMyKSB5b3UgY2FuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZnkgaXQgYnkgZ2l2aW5nIGEgZHluYW1pYyBheGlzIHRvIHRoZSBpbnB1dCAvIG91dHB1dCBsYXllciBieSBpdHMgbmFtZSBhcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xsb3dzOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5wdXQgbGF5ZXIgbmFtZSI6IHswOiAiYmF0Y2hfc2l6ZSJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm91dHB1dCBsYXllciBuYW1lIjogezA6ICJiYXRjaF9zaXplIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgcHJvdmlkZWQsIHRoZSAnaXNfYmF0Y2hlZCcgZmxhZyB3aWxsIGJlIGlnbm9yZWQuIERlZmF1bHRlZCB0byBOb25lLgogICAgICAgIDpwYXJhbSBpc19iYXRjaGVkOiAgICAgICAgICBXaGV0aGVyIHRvIGluY2x1ZGUgYSBiYXRjaCBzaXplIGFzIHRoZSBmaXJzdCBheGlzIGluIGV2ZXJ5IGlucHV0IGFuZCBvdXRwdXQgbGF5ZXIuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBUcnVlLiBXaWxsIGJlIGlnbm9yZWQgaWYgJ2R5bmFtaWNfYXhlcycgaXMgcHJvdmlkZWQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJbXBvcnQgdGhlIGZyYW1ld29yayBhbmQgaGFuZGxlcjoKICAgICAgICBpbXBvcnQgdG9yY2gKICAgICAgICBmcm9tIG1scnVuLmZyYW1ld29ya3MucHl0b3JjaCBpbXBvcnQgUHlUb3JjaFV0aWxzCgogICAgICAgICMgUGFyc2UgdGhlICdpbnB1dF9zaWduYXR1cmUnIHBhcmFtZXRlcjoKICAgICAgICBpZiBpbnB1dF9zaWduYXR1cmUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGlucHV0X3NpZ25hdHVyZSA9IHR1cGxlKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRvcmNoLnplcm9zKAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPXNoYXBlLAogICAgICAgICAgICAgICAgICAgICAgICBkdHlwZT1QeVRvcmNoVXRpbHMuY29udmVydF92YWx1ZV90eXBlX3RvX3RvcmNoX2R0eXBlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVfdHlwZT12YWx1ZV90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGZvciAoc2hhcGUsIHZhbHVlX3R5cGUpIGluIGlucHV0X3NpZ25hdHVyZQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICApCgogICAgICAgICMgQ29udmVydCB0byBPTk5YOgogICAgICAgIG1vZGVsX2hhbmRsZXIudG9fb25ueCgKICAgICAgICAgICAgbW9kZWxfbmFtZT1vbm54X21vZGVsX25hbWUsCiAgICAgICAgICAgIGlucHV0X3NhbXBsZT1pbnB1dF9zaWduYXR1cmUsCiAgICAgICAgICAgIG9wdGltaXplPW9wdGltaXplX21vZGVsLAogICAgICAgICAgICBpbnB1dF9sYXllcnNfbmFtZXM9aW5wdXRfbGF5ZXJzX25hbWVzLAogICAgICAgICAgICBvdXRwdXRfbGF5ZXJzX25hbWVzPW91dHB1dF9sYXllcnNfbmFtZXMsCiAgICAgICAgICAgIGR5bmFtaWNfYXhlcz1keW5hbWljX2F4ZXMsCiAgICAgICAgICAgIGlzX2JhdGNoZWQ9aXNfYmF0Y2hlZCwKICAgICAgICApCgoKIyBNYXAgZm9yIGdldHRpbmcgdGhlIGNvbnZlcnNpb24gZnVuY3Rpb24gYWNjb3JkaW5nIHRvIHRoZSBwcm92aWRlZCBmcmFtZXdvcms6Cl9DT05WRVJTSU9OX01BUCA9IHsKICAgICJ0ZW5zb3JmbG93LmtlcmFzIjogX1RvT05OWENvbnZlcnNpb25zLnRmX2tlcmFzX3RvX29ubngsCiAgICAidG9yY2giOiBfVG9PTk5YQ29udmVyc2lvbnMucHl0b3JjaF90b19vbm54LAp9ICAjIHR5cGU6IERpY3Rbc3RyLCBDYWxsYWJsZV0KCgpkZWYgdG9fb25ueCgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgbW9kZWxfcGF0aDogc3RyLAogICAgbG9hZF9tb2RlbF9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgb25ueF9tb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgb3B0aW1pemVfbW9kZWw6IGJvb2wgPSBUcnVlLAogICAgZnJhbWV3b3JrX2t3YXJnczogRGljdFtzdHIsIEFueV0gPSBOb25lLAopOgogICAgIiIiCiAgICBDb252ZXJ0IHRoZSBnaXZlbiBtb2RlbCB0byBhbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0CiAgICA6cGFyYW0gbW9kZWxfcGF0aDogICAgICAgIFRoZSBtb2RlbCBwYXRoIHN0b3JlIG9iamVjdC4KICAgIDpwYXJhbSBsb2FkX21vZGVsX2t3YXJnczogS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYEF1dG9NTFJ1bi5sb2FkX21vZGVsYCBtZXRob2QuCiAgICA6cGFyYW0gb25ueF9tb2RlbF9uYW1lOiAgIFRoZSBuYW1lIHRvIHVzZSB0byBsb2cgdGhlIGNvbnZlcnRlZCBPTk5YIG1vZGVsLiBJZiBub3QgZ2l2ZW4sIHRoZSBnaXZlbiBgbW9kZWxfbmFtZWAgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkIHdpdGggYW4gYWRkaXRpb25hbCBzdWZmaXggYF9vbm54YC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gb3B0aW1pemVfbW9kZWw6ICAgIFdoZXRoZXIgdG8gb3B0aW1pemUgdGhlIE9OTlggbW9kZWwgdXNpbmcgJ29ubnhvcHRpbWl6ZXInIGJlZm9yZSBzYXZpbmcgdGhlIG1vZGVsLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0ZWQgdG8gVHJ1ZS4KICAgIDpwYXJhbSBmcmFtZXdvcmtfa3dhcmdzOiAgQWRkaXRpb25hbCBhcmd1bWVudHMgZWFjaCBmcmFtZXdvcmsgbWF5IHJlcXVpcmUgdG8gY29udmVydCB0byBPTk5YLiBUbyBnZXQgdGhlIGRvYyBzdHJpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2YgdGhlIGRlc2lyZWQgZnJhbWV3b3JrIG9ubnggY29udmVyc2lvbiBmdW5jdGlvbiwgcGFzcyAiaGVscCIuCiAgICAiIiIKICAgIGZyb20gbWxydW4uZnJhbWV3b3Jrcy5hdXRvX21scnVuLmF1dG9fbWxydW4gaW1wb3J0IEF1dG9NTFJ1bgoKICAgICMgR2V0IGEgbW9kZWwgaGFuZGxlciBvZiB0aGUgcmVxdWlyZWQgZnJhbWV3b3JrOgogICAgbG9hZF9tb2RlbF9rd2FyZ3MgPSBsb2FkX21vZGVsX2t3YXJncyBvciB7fQogICAgbW9kZWxfaGFuZGxlciA9IEF1dG9NTFJ1bi5sb2FkX21vZGVsKAogICAgICAgIG1vZGVsX3BhdGg9bW9kZWxfcGF0aCwgY29udGV4dD1jb250ZXh0LCAqKmxvYWRfbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBHZXQgdGhlIG1vZGVsJ3MgZnJhbWV3b3JrOgogICAgZnJhbWV3b3JrID0gbW9kZWxfaGFuZGxlci5GUkFNRVdPUktfTkFNRQoKICAgICMgVXNlIHRoZSBjb252ZXJzaW9uIG1hcCB0byBnZXQgdGhlIHNwZWNpZmljIGZyYW1ld29yayB0byBvbm54IGNvbnZlcnNpb246CiAgICBpZiBmcmFtZXdvcmsgbm90IGluIF9DT05WRVJTSU9OX01BUDoKICAgICAgICByYWlzZSBtbHJ1bi5lcnJvcnMuTUxSdW5JbnZhbGlkQXJndW1lbnRFcnJvcigKICAgICAgICAgICAgZiJUaGUgZm9sbG93aW5nIGZyYW1ld29yazogJ3tmcmFtZXdvcmt9JywgaGFzIG5vIE9OTlggY29udmVyc2lvbi4iCiAgICAgICAgKQogICAgY29udmVyc2lvbl9mdW5jdGlvbiA9IF9DT05WRVJTSU9OX01BUFtmcmFtZXdvcmtdCgogICAgIyBDaGVjayBpZiBuZWVkZWQgdG8gcHJpbnQgdGhlIGZ1bmN0aW9uJ3MgZG9jIHN0cmluZyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzID09ICJoZWxwIjoKICAgICAgICBwcmludChjb252ZXJzaW9uX2Z1bmN0aW9uLl9fZG9jX18pCiAgICAgICAgcmV0dXJuCgogICAgIyBTZXQgdGhlIGRlZmF1bHQgZW1wdHkgZnJhbWV3b3JrIGt3YXJncyBpZiBuZWVkZWQ6CiAgICBpZiBmcmFtZXdvcmtfa3dhcmdzIGlzIE5vbmU6CiAgICAgICAgZnJhbWV3b3JrX2t3YXJncyA9IHt9CgogICAgIyBSdW4gdGhlIGNvbnZlcnNpb246CiAgICB0cnk6CiAgICAgICAgY29udmVyc2lvbl9mdW5jdGlvbigKICAgICAgICAgICAgbW9kZWxfaGFuZGxlcj1tb2RlbF9oYW5kbGVyLAogICAgICAgICAgICBvbm54X21vZGVsX25hbWU9b25ueF9tb2RlbF9uYW1lLAogICAgICAgICAgICBvcHRpbWl6ZV9tb2RlbD1vcHRpbWl6ZV9tb2RlbCwKICAgICAgICAgICAgKipmcmFtZXdvcmtfa3dhcmdzLAogICAgICAgICkKICAgIGV4Y2VwdCBUeXBlRXJyb3IgYXMgZXhjZXB0aW9uOgogICAgICAgIHJhaXNlIG1scnVuLmVycm9ycy5NTFJ1bkludmFsaWRBcmd1bWVudEVycm9yKAogICAgICAgICAgICBmIkVSUk9SOiBBIFR5cGVFcnJvciBleGNlcHRpb24gd2FzIHJhaXNlZCBkdXJpbmcgdGhlIGNvbnZlcnNpb246XG57ZXhjZXB0aW9ufS4gIgogICAgICAgICAgICBmIlBsZWFzZSByZWFkIHRoZSB7ZnJhbWV3b3JrfSBmcmFtZXdvcmsgY29udmVyc2lvbiBmdW5jdGlvbiBkb2Mgc3RyaW5nIGJ5IHBhc3NpbmcgJ2hlbHAnIGluIHRoZSAiCiAgICAgICAgICAgIGYiJ2ZyYW1ld29ya19rd2FyZ3MnIGRpY3Rpb25hcnkgcGFyYW1ldGVyLiIKICAgICAgICApCgoKZGVmIG9wdGltaXplKAogICAgY29udGV4dDogbWxydW4uTUxDbGllbnRDdHgsCiAgICBtb2RlbF9wYXRoOiBzdHIsCiAgICBoYW5kbGVyX2luaXRfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIG9wdGltaXphdGlvbnM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBmaXhlZF9wb2ludDogYm9vbCA9IEZhbHNlLAogICAgb3B0aW1pemVkX21vZGVsX25hbWU6IHN0ciA9IE5vbmUsCik6CiAgICAiIiIKICAgIE9wdGltaXplIHRoZSBnaXZlbiBPTk5YIG1vZGVsLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGZ1bmN0aW9uIGV4ZWN1dGlvbiBjb250ZXh0LgogICAgOnBhcmFtIG1vZGVsX3BhdGg6ICAgICAgICAgICBQYXRoIHRvIHRoZSBPTk5YIG1vZGVsIG9iamVjdC4KICAgIDpwYXJhbSBoYW5kbGVyX2luaXRfa3dhcmdzOiAgS2V5d29yZCBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgYE9OTlhNb2RlbEhhbmRsZXJgIGluaXQgbWV0aG9kIHByZWxvYWRpbmcuCiAgICA6cGFyYW0gb3B0aW1pemF0aW9uczogICAgICAgIExpc3Qgb2YgcG9zc2libGUgb3B0aW1pemF0aW9ucy4gVG8gc2VlIHdoYXQgb3B0aW1pemF0aW9ucyBhcmUgYXZhaWxhYmxlLCBwYXNzICJoZWxwIi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSWYgTm9uZSwgYWxsIHRoZSBvcHRpbWl6YXRpb25zIHdpbGwgYmUgdXNlZC4gRGVmYXVsdGVkIHRvIE5vbmUuCiAgICA6cGFyYW0gZml4ZWRfcG9pbnQ6ICAgICAgICAgIE9wdGltaXplIHRoZSB3ZWlnaHRzIHVzaW5nIGZpeGVkIHBvaW50LiBEZWZhdWx0ZWQgdG8gRmFsc2UuCiAgICA6cGFyYW0gb3B0aW1pemVkX21vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcHRpbWl6ZWQgbW9kZWwuIElmIE5vbmUsIHRoZSBvcmlnaW5hbCBtb2RlbCB3aWxsIGJlIG92ZXJyaWRkZW4uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHRlZCB0byBOb25lLgogICAgIiIiCiAgICAjIEltcG9ydCB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGltcG9ydCBvbm54b3B0aW1pemVyCiAgICBmcm9tIG1scnVuLmZyYW1ld29ya3Mub25ueCBpbXBvcnQgT05OWE1vZGVsSGFuZGxlcgoKICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIHByaW50IHRoZSBhdmFpbGFibGUgb3B0aW1pemF0aW9ucyAoImhlbHAiIGlzIHBhc3NlZCk6CiAgICBpZiBvcHRpbWl6YXRpb25zID09ICJoZWxwIjoKICAgICAgICBhdmFpbGFibGVfcGFzc2VzID0gIlxuKiAiLmpvaW4ob25ueG9wdGltaXplci5nZXRfYXZhaWxhYmxlX3Bhc3NlcygpKQogICAgICAgIHByaW50KGYiVGhlIGF2YWlsYWJsZSBvcHRpbWl6YXRpb25zIGFyZTpcbioge2F2YWlsYWJsZV9wYXNzZXN9IikKICAgICAgICByZXR1cm4KCiAgICAjIENyZWF0ZSB0aGUgbW9kZWwgaGFuZGxlcjoKICAgIGhhbmRsZXJfaW5pdF9rd2FyZ3MgPSBoYW5kbGVyX2luaXRfa3dhcmdzIG9yIHt9CiAgICBtb2RlbF9oYW5kbGVyID0gT05OWE1vZGVsSGFuZGxlcigKICAgICAgICBtb2RlbF9wYXRoPW1vZGVsX3BhdGgsIGNvbnRleHQ9Y29udGV4dCwgKipoYW5kbGVyX2luaXRfa3dhcmdzCiAgICApCgogICAgIyBMb2FkIHRoZSBPTk5YIG1vZGVsOgogICAgbW9kZWxfaGFuZGxlci5sb2FkKCkKCiAgICAjIE9wdGltaXplIHRoZSBtb2RlbCB1c2luZyB0aGUgZ2l2ZW4gY29uZmlndXJhdGlvbnM6CiAgICBtb2RlbF9oYW5kbGVyLm9wdGltaXplKG9wdGltaXphdGlvbnM9b3B0aW1pemF0aW9ucywgZml4ZWRfcG9pbnQ9Zml4ZWRfcG9pbnQpCgogICAgIyBSZW5hbWUgaWYgbmVlZGVkOgogICAgaWYgb3B0aW1pemVkX21vZGVsX25hbWUgaXMgbm90IE5vbmU6CiAgICAgICAgbW9kZWxfaGFuZGxlci5zZXRfbW9kZWxfbmFtZShtb2RlbF9uYW1lPW9wdGltaXplZF9tb2RlbF9uYW1lKQoKICAgICMgTG9nIHRoZSBvcHRpbWl6ZWQgbW9kZWw6CiAgICBtb2RlbF9oYW5kbGVyLmxvZygpCg==
-    auto_build: true
-    base_image: mlrun/mlrun
-    with_mlrun: false
-    requirements:
-    - tqdm~=4.67.1
-    - tensorflow~=2.19.0
-    - tf_keras~=2.19.0
-    - torch~=2.6.0
-    - torchvision~=0.21.0
-    - onnx~=1.17.0
-    - onnxruntime~=1.19.2
-    - onnxoptimizer~=0.3.13
-    - onnxmltools~=1.13.0
-    - tf2onnx~=1.16.1
-    - plotly~=5.4.0
-    origin_filename: ''
-  disable_auto_mount: false
-metadata:
-  categories:
-  - utils
-  tag: ''
-  name: onnx-utils
-verbose: false
-kind: job
+      has_kwargs: false
+      lineno: 224
+  default_handler: to_onnx
+  allow_empty_resources: true
+  command: ''
 
         
     
diff --git a/functions/master/onnx_utils/latest/static/item.html b/functions/master/onnx_utils/latest/static/item.html index ab6f22d2..b575e949 100644 --- a/functions/master/onnx_utils/latest/static/item.html +++ b/functions/master/onnx_utils/latest/static/item.html @@ -31,6 +31,7 @@ apiVersion: v1 categories: - utils +- deep-learning description: ONNX intigration in MLRun, some utils functions for the ONNX framework, optimizing and converting models from different framework to ONNX using MLRun. doc: '' diff --git a/functions/master/onnx_utils/latest/static/onnx_utils.html b/functions/master/onnx_utils/latest/static/onnx_utils.html index c1964206..297beedb 100644 --- a/functions/master/onnx_utils/latest/static/onnx_utils.html +++ b/functions/master/onnx_utils/latest/static/onnx_utils.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/1.2.0/src/function.yaml b/functions/master/open_archive/1.2.0/src/function.yaml index dee623a0..bf78b5fc 100644 --- a/functions/master/open_archive/1.2.0/src/function.yaml +++ b/functions/master/open_archive/1.2.0/src/function.yaml @@ -1,22 +1,20 @@ kind: job -metadata: - name: open-archive - categories: - - data-preparation - tag: '' verbose: false spec: + command: '' + disable_auto_mount: false + default_handler: open_archive build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA== code_origin: '' origin_filename: '' - functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA== - default_handler: open_archive - command: '' - image: mlrun/mlrun description: Open a file/object archive into a target directory + image: mlrun/mlrun entry_points: open_archive: has_kwargs: false + lineno: 27 + name: open_archive parameters: - name: context type: MLClientCtx @@ -40,6 +38,8 @@ spec: doc: Open a file/object archive into a target directory. Currently, supports zip and tar.gz. has_varargs: false - name: open_archive - lineno: 27 - disable_auto_mount: false +metadata: + name: open-archive + categories: + - utils + tag: '' diff --git a/functions/master/open_archive/1.2.0/src/item.yaml b/functions/master/open_archive/1.2.0/src/item.yaml index 35b5e147..0a2f4516 100644 --- a/functions/master/open_archive/1.2.0/src/item.yaml +++ b/functions/master/open_archive/1.2.0/src/item.yaml @@ -1,6 +1,6 @@ apiVersion: v1 categories: -- data-preparation +- utils description: Open a file/object archive into a target directory doc: '' example: open_archive.ipynb diff --git a/functions/master/open_archive/1.2.0/static/documentation.html b/functions/master/open_archive/1.2.0/static/documentation.html index 8d47370a..316b0576 100644 --- a/functions/master/open_archive/1.2.0/static/documentation.html +++ b/functions/master/open_archive/1.2.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/1.2.0/static/example.html b/functions/master/open_archive/1.2.0/static/example.html index c7b8d345..567eaf34 100644 --- a/functions/master/open_archive/1.2.0/static/example.html +++ b/functions/master/open_archive/1.2.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/1.2.0/static/function.html b/functions/master/open_archive/1.2.0/static/function.html index e6348802..16dd7fa7 100644 --- a/functions/master/open_archive/1.2.0/static/function.html +++ b/functions/master/open_archive/1.2.0/static/function.html @@ -29,24 +29,22 @@
         
 kind: job
-metadata:
-  name: open-archive
-  categories:
-  - data-preparation
-  tag: ''
 verbose: false
 spec:
+  command: ''
+  disable_auto_mount: false
+  default_handler: open_archive
   build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA==
     code_origin: ''
     origin_filename: ''
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA==
-  default_handler: open_archive
-  command: ''
-  image: mlrun/mlrun
   description: Open a file/object archive into a target directory
+  image: mlrun/mlrun
   entry_points:
     open_archive:
       has_kwargs: false
+      lineno: 27
+      name: open_archive
       parameters:
       - name: context
         type: MLClientCtx
@@ -70,9 +68,11 @@
       doc: Open a file/object archive into a target directory. Currently, supports
         zip and tar.gz.
       has_varargs: false
-      name: open_archive
-      lineno: 27
-  disable_auto_mount: false
+metadata:
+  name: open-archive
+  categories:
+  - utils
+  tag: ''
 
         
     
diff --git a/functions/master/open_archive/1.2.0/static/item.html b/functions/master/open_archive/1.2.0/static/item.html index 0e81169e..fed83413 100644 --- a/functions/master/open_archive/1.2.0/static/item.html +++ b/functions/master/open_archive/1.2.0/static/item.html @@ -30,7 +30,7 @@ apiVersion: v1 categories: -- data-preparation +- utils description: Open a file/object archive into a target directory doc: '' example: open_archive.ipynb diff --git a/functions/master/open_archive/1.2.0/static/open_archive.html b/functions/master/open_archive/1.2.0/static/open_archive.html index ec8542bd..5aa03a6d 100644 --- a/functions/master/open_archive/1.2.0/static/open_archive.html +++ b/functions/master/open_archive/1.2.0/static/open_archive.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/latest/src/function.yaml b/functions/master/open_archive/latest/src/function.yaml index dee623a0..bf78b5fc 100644 --- a/functions/master/open_archive/latest/src/function.yaml +++ b/functions/master/open_archive/latest/src/function.yaml @@ -1,22 +1,20 @@ kind: job -metadata: - name: open-archive - categories: - - data-preparation - tag: '' verbose: false spec: + command: '' + disable_auto_mount: false + default_handler: open_archive build: + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA== code_origin: '' origin_filename: '' - functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA== - default_handler: open_archive - command: '' - image: mlrun/mlrun description: Open a file/object archive into a target directory + image: mlrun/mlrun entry_points: open_archive: has_kwargs: false + lineno: 27 + name: open_archive parameters: - name: context type: MLClientCtx @@ -40,6 +38,8 @@ spec: doc: Open a file/object archive into a target directory. Currently, supports zip and tar.gz. has_varargs: false - name: open_archive - lineno: 27 - disable_auto_mount: false +metadata: + name: open-archive + categories: + - utils + tag: '' diff --git a/functions/master/open_archive/latest/src/item.yaml b/functions/master/open_archive/latest/src/item.yaml index 35b5e147..0a2f4516 100644 --- a/functions/master/open_archive/latest/src/item.yaml +++ b/functions/master/open_archive/latest/src/item.yaml @@ -1,6 +1,6 @@ apiVersion: v1 categories: -- data-preparation +- utils description: Open a file/object archive into a target directory doc: '' example: open_archive.ipynb diff --git a/functions/master/open_archive/latest/static/documentation.html b/functions/master/open_archive/latest/static/documentation.html index 8d47370a..316b0576 100644 --- a/functions/master/open_archive/latest/static/documentation.html +++ b/functions/master/open_archive/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/latest/static/example.html b/functions/master/open_archive/latest/static/example.html index c7b8d345..567eaf34 100644 --- a/functions/master/open_archive/latest/static/example.html +++ b/functions/master/open_archive/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/open_archive/latest/static/function.html b/functions/master/open_archive/latest/static/function.html index e6348802..16dd7fa7 100644 --- a/functions/master/open_archive/latest/static/function.html +++ b/functions/master/open_archive/latest/static/function.html @@ -29,24 +29,22 @@
         
 kind: job
-metadata:
-  name: open-archive
-  categories:
-  - data-preparation
-  tag: ''
 verbose: false
 spec:
+  command: ''
+  disable_auto_mount: false
+  default_handler: open_archive
   build:
+    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA==
     code_origin: ''
     origin_filename: ''
-    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNSBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCiMKIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCiMKCmltcG9ydCBvcwppbXBvcnQgemlwZmlsZQppbXBvcnQgdGFyZmlsZQoKZnJvbSBtbHJ1bi5leGVjdXRpb24gaW1wb3J0IE1MQ2xpZW50Q3R4CmZyb20gbWxydW4uZGF0YXN0b3JlIGltcG9ydCBEYXRhSXRlbQpmcm9tIG1scnVuLmFydGlmYWN0cy5iYXNlIGltcG9ydCBEaXJBcnRpZmFjdAoKZnJvbSB1cmxsaWIucGFyc2UgaW1wb3J0IHVybHBhcnNlCgoKZGVmIG9wZW5fYXJjaGl2ZSgKICAgICAgICBjb250ZXh0OiBNTENsaWVudEN0eCwKICAgICAgICBhcmNoaXZlX3VybDogRGF0YUl0ZW0sCiAgICAgICAgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLAogICAgICAgIGtleTogc3RyID0gImNvbnRlbnQiLAogICAgICAgIHRhcmdldF9wYXRoOiBzdHIgPSBOb25lLAopOgogICAgIiIiT3BlbiBhIGZpbGUvb2JqZWN0IGFyY2hpdmUgaW50byBhIHRhcmdldCBkaXJlY3RvcnkuIEN1cnJlbnRseSwgc3VwcG9ydHMgemlwIGFuZCB0YXIuZ3ouCgogICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbnRleHQKICAgIDpwYXJhbSBhcmNoaXZlX3VybDogIHVybCBvZiBhcmNoaXZlIGZpbGUKICAgIDpwYXJhbSBzdWJkaXI6ICAgICAgIHBhdGggd2l0aGluIGFydGlmYWN0IHN0b3JlIHdoZXJlIGV4dHJhY3RlZCBmaWxlcyBhcmUgc3RvcmVkLCBkZWZhdWx0IGlzICIvY29udGVudCIKICAgIDpwYXJhbSBrZXk6ICAgICAgICAgIGtleSBvZiBhcmNoaXZlIGNvbnRlbnRzIGluIGFydGlmYWN0IHN0b3JlCiAgICA6cGFyYW0gdGFyZ2V0X3BhdGg6ICBmaWxlIHN5c3RlbSBwYXRoIHRvIHN0b3JlIGV4dHJhY3RlZCBmaWxlcwogICAgIiIiCgogICAgIyBSZXNvbHZlcyB0aGUgYXJjaGl2ZSBsb2NhbGx5CiAgICBhcmNoaXZlX3VybCA9IGFyY2hpdmVfdXJsLmxvY2FsKCkKICAgIHYzaW9fc3ViZGlyID0gTm9uZQogICAgIyBXaGVuIGN1c3RvbSBhcnRpZmFjdCBwYXRoIGlzIGRlZmluZWQKICAgIGlmIG5vdCB0YXJnZXRfcGF0aCBhbmQgY29udGV4dC5hcnRpZmFjdF9wYXRoOgogICAgICAgIHBhcnNlZF9zdWJkaXIgPSB1cmxwYXJzZShjb250ZXh0LmFydGlmYWN0X3BhdGgpCiAgICAgICAgaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3MzJzoKICAgICAgICAgICAgc3ViZGlyID0gb3MucGF0aC5qb2luKGNvbnRleHQuYXJ0aWZhY3RfcGF0aCwgc3ViZGlyKQogICAgICAgIGVsaWYgcGFyc2VkX3N1YmRpci5zY2hlbWUgPT0gJ3YzaW8nOgogICAgICAgICAgICB2M2lvX3N1YmRpciA9IG9zLnBhdGguam9pbihjb250ZXh0LmFydGlmYWN0X3BhdGgsIHN1YmRpcikgICMgVXNpbmcgdjNpb19zdWJkaXIgZm9yIGxvZ2dpbmcKICAgICAgICAgICAgc3ViZGlyID0gJy92M2lvJyArIHBhcnNlZF9zdWJkaXIucGF0aCArICcvJyArIHN1YmRpcgogICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgdjNpbyBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oZidVbnJlY29nbml6YWJsZSBzY2hlbWUsIGV4dHJhY3RpbmcgdG8ge3N1YmRpcn0nKQoKICAgICMgV2hlbiB3b3JraW5nIG9uIENFLCB0YXJnZXQgcGF0aCBtaWdodCBiZSBvbiBzMwogICAgaWYgJ3MzJyBpbiAodGFyZ2V0X3BhdGggb3Igc3ViZGlyKToKICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnVXNpbmcgczMgc2NoZW1lLCBleHRyYWN0aW5nIHRvIHt0YXJnZXRfcGF0aCBvciBzdWJkaXJ9JykKCiAgICAgICAgaWYgYXJjaGl2ZV91cmwuZW5kc3dpdGgoImd6Iik6CiAgICAgICAgICAgIF9leHRyYWN0X2d6X2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQoKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoLCBpbl9zMz1UcnVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCiAgICBlbHNlOgogICAgICAgIGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJneiIpOgogICAgICAgICAgICBfZXh0cmFjdF9nel9maWxlKGFyY2hpdmVfdXJsPWFyY2hpdmVfdXJsLCBzdWJkaXI9c3ViZGlyLCB0YXJnZXRfcGF0aD10YXJnZXRfcGF0aCkKICAgICAgICBlbGlmIGFyY2hpdmVfdXJsLmVuZHN3aXRoKCJ6aXAiKToKICAgICAgICAgICAgX2V4dHJhY3RfemlwX2ZpbGUoYXJjaGl2ZV91cmw9YXJjaGl2ZV91cmwsIHN1YmRpcj1zdWJkaXIsIHRhcmdldF9wYXRoPXRhcmdldF9wYXRoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJ1bnN1cHBvcnRlZCBhcmNoaXZlIHR5cGUgaW4ge2FyY2hpdmVfdXJsfSIpCgogICAgaWYgdjNpb19zdWJkaXI6CiAgICAgICAgc3ViZGlyID0gdjNpb19zdWJkaXIKCiAgICBjb250ZXh0LmxvZ2dlci5pbmZvKGYnTG9nZ2luZyBhcnRpZmFjdCB0byB7KHRhcmdldF9wYXRoIG9yIHN1YmRpcil9JykKICAgIGNvbnRleHQubG9nX2FydGlmYWN0KERpckFydGlmYWN0KGtleT1rZXksIHRhcmdldF9wYXRoPSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpKSkKCgpkZWYgX2V4dHJhY3RfZ3pfZmlsZShhcmNoaXZlX3VybDogc3RyLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InJ8Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBtZW1iZXIgaW4gcmVmLmdldG1lbWJlcnMoKToKICAgICAgICAgICAgICAgIGRhdGEgPSByZWYuZXh0cmFjdGZpbGUobWVtYmVyPW1lbWJlcikucmVhZCgpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXttZW1iZXIubmFtZX0nKQogICAgZWxzZToKICAgICAgICBvcy5tYWtlZGlycyh0YXJnZXRfcGF0aCBvciBzdWJkaXIsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4oYXJjaGl2ZV91cmwsIG1vZGU9InI6Z3oiKSBhcyByZWY6CiAgICAgICAgICAgIGZvciBlbnRyeSBpbiByZWY6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIHRoYXQgdGhlcmUgaXMgbm8gcGF0aCB0cmF2ZXJzYWwgaW4gdGhlIGFyY2hpdmUKICAgICAgICAgICAgICAgIGlmIG9zLnBhdGguaXNhYnMoZW50cnkubmFtZSkgb3IgIi4uIiBpbiBlbnRyeS5uYW1lOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHRhciBhcmNoaXZlIGVudHJ5OiB7ZW50cnkubmFtZX0iKQoKICAgICAgICAgICAgICAgIHJlZi5leHRyYWN0KGVudHJ5LCB0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9leHRyYWN0X3ppcF9maWxlKGFyY2hpdmVfdXJsLCB0YXJnZXRfcGF0aDogc3RyID0gTm9uZSwgc3ViZGlyOiBzdHIgPSAiY29udGVudC8iLCBpbl9zMzogYm9vbCA9IEZhbHNlKToKICAgIGlmIGluX3MzOgogICAgICAgIGNsaWVudCA9IF9pbml0X2JvdG8zX2NsaWVudCgpCiAgICAgICAgd2l0aCB6aXBmaWxlLlppcEZpbGUoYXJjaGl2ZV91cmwsICJyIikgYXMgcmVmOgogICAgICAgICAgICBmb3IgZmlsZW5hbWUgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBkYXRhID0gcmVmLnJlYWQoZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBjbGllbnQucHV0X29iamVjdChCb2R5PWRhdGEsIEJ1Y2tldD11cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLm5ldGxvYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEtleT1mJ3t1cmxwYXJzZSh0YXJnZXRfcGF0aCBvciBzdWJkaXIpLnBhdGhbMTpdfXtmaWxlbmFtZX0nKQogICAgZWxzZToKICAgICAgICB3aXRoIHppcGZpbGUuWmlwRmlsZShhcmNoaXZlX3VybCwgInIiKSBhcyByZWY6CiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhhdCB0aGVyZSBpcyBubyBwYXRoIHRyYXZlcnNhbCBpbiB0aGUgYXJjaGl2ZQogICAgICAgICAgICBmb3IgZW50cnkgaW4gcmVmLm5hbWVsaXN0KCk6CiAgICAgICAgICAgICAgICBpZiBvcy5wYXRoLmlzYWJzKGVudHJ5KSBvciAiLi4iIGluIGVudHJ5OgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJJbGxlZ2FsIHppcCBhcmNoaXZlIGVudHJ5OiB7ZW50cnl9IikKICAgICAgICAgICAgb3MubWFrZWRpcnModGFyZ2V0X3BhdGggb3Igc3ViZGlyLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICByZWYuZXh0cmFjdGFsbCh0YXJnZXRfcGF0aCBvciBzdWJkaXIpCgoKZGVmIF9pbml0X2JvdG8zX2NsaWVudCgpOgogICAgaW1wb3J0IGJvdG8zCiAgICBpZiBvcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJyk6CiAgICAgICAgY2xpZW50ID0gYm90bzMuY2xpZW50KCdzMycsIGVuZHBvaW50X3VybD1vcy5lbnZpcm9uLmdldCgnUzNfRU5EUE9JTlRfVVJMJykpCiAgICBlbHNlOgogICAgICAgIGNsaWVudCA9IGJvdG8zLmNsaWVudCgnczMnKQogICAgcmV0dXJuIGNsaWVudA==
-  default_handler: open_archive
-  command: ''
-  image: mlrun/mlrun
   description: Open a file/object archive into a target directory
+  image: mlrun/mlrun
   entry_points:
     open_archive:
       has_kwargs: false
+      lineno: 27
+      name: open_archive
       parameters:
       - name: context
         type: MLClientCtx
@@ -70,9 +68,11 @@
       doc: Open a file/object archive into a target directory. Currently, supports
         zip and tar.gz.
       has_varargs: false
-      name: open_archive
-      lineno: 27
-  disable_auto_mount: false
+metadata:
+  name: open-archive
+  categories:
+  - utils
+  tag: ''
 
         
     
diff --git a/functions/master/open_archive/latest/static/item.html b/functions/master/open_archive/latest/static/item.html index 0e81169e..fed83413 100644 --- a/functions/master/open_archive/latest/static/item.html +++ b/functions/master/open_archive/latest/static/item.html @@ -30,7 +30,7 @@ apiVersion: v1 categories: -- data-preparation +- utils description: Open a file/object archive into a target directory doc: '' example: open_archive.ipynb diff --git a/functions/master/open_archive/latest/static/open_archive.html b/functions/master/open_archive/latest/static/open_archive.html index ec8542bd..5aa03a6d 100644 --- a/functions/master/open_archive/latest/static/open_archive.html +++ b/functions/master/open_archive/latest/static/open_archive.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pii_recognizer/0.4.0/src/data/config.csv b/functions/master/pii_recognizer/0.4.0/src/data/config.csv new file mode 100644 index 00000000..fe2c350e --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/data/config.csv @@ -0,0 +1,3 @@ +input_file,output_file +data/pii.txt,data/pii_out.txt +data/letter.txt,data/letter_out.txt diff --git a/functions/master/pii_recognizer/0.4.0/src/data/letter.txt b/functions/master/pii_recognizer/0.4.0/src/data/letter.txt new file mode 100644 index 00000000..59d25e78 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/data/letter.txt @@ -0,0 +1,12 @@ +Dear Mr. John Doe, + +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of Riviera. Your flight tickets have been booked, and you will be departing on July 15th, 2023. + +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations. + +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team. + +Best regards, + +Jane Smith +Customer Support Representative diff --git a/functions/master/pii_recognizer/0.4.0/src/data/output/letter_output.txt b/functions/master/pii_recognizer/0.4.0/src/data/output/letter_output.txt new file mode 100644 index 00000000..468533af --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/data/output/letter_output.txt @@ -0,0 +1,12 @@ +Dear Mr. , + +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of . Your flight tickets have been booked, and you will be departing on July 15th, 2023. + +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations. + +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team. + +Best regards, + + + Support diff --git a/functions/master/pii_recognizer/0.4.0/src/data/output/pii_output.txt b/functions/master/pii_recognizer/0.4.0/src/data/output/pii_output.txt new file mode 100644 index 00000000..1160e497 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/data/output/pii_output.txt @@ -0,0 +1 @@ + is , connect him with or , he can pay you with \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/src/data/pii.txt b/functions/master/pii_recognizer/0.4.0/src/data/pii.txt new file mode 100644 index 00000000..8886cc08 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/data/pii.txt @@ -0,0 +1 @@ +John smith's ssn is 182838483, connect him with John_smith@gmail.com or 6288389029, he can pay you with 41482929939393 \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/src/function.yaml b/functions/master/pii_recognizer/0.4.0/src/function.yaml new file mode 100644 index 00000000..e7d6c124 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/function.yaml @@ -0,0 +1,115 @@ +verbose: false +spec: + default_handler: recognize_pii + entry_points: + analyze: + name: analyze + outputs: + - doc: The list of Presidio RecognizerResult constructed from the recognized + Flair detections. + type: List[pa.RecognizerResult] + has_kwargs: false + parameters: + - name: self + - name: text + type: str + doc: The text for analysis. + - name: entities + type: List[str] + doc: The list of entities to recognize. + - name: nlp_artifacts + type: pa.nlp_engine.NlpArtifacts + doc: Not used by this recognizer but needed for the interface. + default: null + lineno: 381 + doc: Analyze text and return the results. + has_varargs: false + recognize_pii: + name: recognize_pii + outputs: + - doc: 'A tuple of:' + type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, + dict]] + has_kwargs: false + parameters: + - name: context + type: MLClientCtx + doc: The MLRun context. this is needed for log the artifacts. + - name: input_path + type: Union[str, Path] + doc: The input path of the text files needs to be analyzed. + - name: html_key + type: str + doc: The html key for the artifact. + - name: score_threshold + type: float + doc: The score threshold to mark the recognition as trusted. + - name: output_directory + type: str + doc: The output directory path to store the anonymized text. + default: null + - name: entities + type: List[str] + doc: The list of entities to recognize. + default: null + - name: entity_operator_map + type: dict + doc: The map of entity to operator (mask, redact, replace, keep, hash, and + its params) + default: null + - name: model + type: str + doc: The model to use. Can be "spacy", "flair", "pattern" or "whole". + default: null + - name: generate_json + type: bool + doc: Whether to generate the json report of the explanation. + default: true + - name: generate_html + type: bool + doc: Whether to generate the html report of the explanation. + default: true + - name: is_full_text + type: bool + doc: Whether to return the full text or only the masked text. + default: true + - name: is_full_html + type: bool + doc: Whether to return the full html or just the annotated text + default: true + - name: is_full_report + type: bool + doc: Whether to return the full report or just the score and start, end index + default: true + lineno: 845 + doc: 'Walk through the input path, recognize PII in text and store the anonymized + text in the output path. + + Generate the html with different colors for each entity, json report of the + explanation.' + has_varargs: false + build: + base_image: mlrun/mlrun + requirements: + - nltk + - pandas + - presidio-anonymizer + - presidio-analyzer + - torch + - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653 + - st-annotated-text + - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo= + code_origin: '' + origin_filename: '' + description: This function is used to recognize PII in a directory of text files + image: '' + command: '' + disable_auto_mount: false +kind: job +metadata: + name: pii-recognizer + tag: '' + categories: + - data-preparation + - NLP diff --git a/functions/master/pii_recognizer/0.4.0/src/item.yaml b/functions/master/pii_recognizer/0.4.0/src/item.yaml new file mode 100644 index 00000000..8f3185b4 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/item.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +categories: + - data-preparation + - NLP +description: This function is used to recognize PII in a directory of text files +doc: '' +example: pii_recognizer.ipynb +generationDate: 2023-08-15:10-24 +hidden: false +icon: '' +labels: + author: pgw +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: pii-recognizer +platformVersion: 3.5.3 +spec: + filename: pii_recognizer.py + handler: recognize_pii + image: mlrun/mlrun + kind: job + requirements: + - nltk + - pandas + - presidio-anonymizer + - presidio-analyzer + - torch + - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653 + - st-annotated-text + - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl +url: '' +version: 0.4.0 +test_valid: False diff --git a/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.ipynb b/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.ipynb new file mode 100644 index 00000000..48d1100d --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.ipynb @@ -0,0 +1,2015 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7412335f", + "metadata": {}, + "source": [ + "# PII Recognizer\n", + "\n", + "A function to detect pii data and anonymize the pii entity in the text. \n", + "\n", + "In this notebook we will go over the function's docs and outputs and see an end-to-end example of running it.\n", + "\n", + "1. [Documentation](#chapter1)\n", + "2. [Results](#chapter2)\n", + "3. [End-to-end Demo](#chapter3)" + ] + }, + { + "cell_type": "markdown", + "id": "0bb6c621", + "metadata": {}, + "source": [ + "\n", + "## 1. Documentation\n", + "\n", + "The function receive a directory path with all the text files in it. It walk through the directory, get all the text file. Then it detect the pii entity inside of the text file, apply the operator on the entity. Generate the html file with all pii entity highlighted. Generate the json report has the explaination of the process.\n" + ] + }, + { + "cell_type": "markdown", + "id": "de1a1349", + "metadata": {}, + "source": [ + "### 1.1. Parameters:\n", + "* **context**: `mlrun.MLClientCtx`\n", + " \n", + " The MLRun context\n", + " \n", + "* **input_path**: `str`\n", + " \n", + " The input directory with all the text files\n", + " \n", + "* **output_path**: `str`\n", + " \n", + " The directory that is used to store the anonymized text files. it is also used for mlrun to log the artifact as zip file\n", + " \n", + "* **output_suffix**: `str`\n", + " \n", + " The suffix will added to the input file. for example if the input text file is pii.txt, if output_suffix is \"anonymized\", the output file would be pii_anonymized.txt\n", + " \n", + "* **html_key**: `str`\n", + " \n", + " The artifact name of the html file \n", + " \n", + "* **entities**: `List[str]`\n", + " \n", + " The list of the entities to recognize. Please make sure the model you choose can recognize the entities. \n", + "\n", + "* **entity_operator_map**: `List[str]`\n", + " For different entity, we can apply different operator. Now supports Keep, Mask, Replace, Redact, Hash\n", + " \n", + "
\n",
+    "     entity_operator_map = {\n",
+    "        \"PERSON\": (\"keep\", {}),\n",
+    "        \"EMAIL\": (\"mask\", {\"masking_char\": \"#\", \"chars_to_mask\": 5, \"from_end\": False}),\n",
+    "        \"PHONE\": (\"hash\", {}),\n",
+    "        \"LOCATION\": (\"redact\", {}),\n",
+    "        \"ORGANIZATION\": (\"replace\", {\"new_value\": \"Company XYZ\"})\n",
+    "        }\n",
+    "     
\n", + " \n", + " In this example:\n", + "\n", + " - \"PERSON\" entities are kept as they are using the \"keep\" operator. \n", + " - \"EMAIL_ADDRESS\" entities are masked with the \"#\" character, masking the first five characters. \n", + " - \"PHONE_NUMBER\" entities are replaced with their hashed value using the \"hash\" operator.\n", + " - \"LOCATION\" entities are completely removed using the \"redact\" operator.\n", + " - \"ORGANIZATION\" entities are replaced with the string \"Company XYZ\" using the \"replace\" operator.\n", + " \n", + "* **model**: `str`\n", + " \n", + " - \"whole\", \"spacy\", \"pattern\", \"flair\". The default is \"whole\".\n", + " \n", + " For each model, it can detect some entities. The \"whole\" model is combined all three models together. It can detect all the entities list below. \n", + " \n", + " \n", + " - \"spacy\" : [\"LOCATION\", \"PERSON\",\"NRP\",\"ORGANIZATION\",\"DATE_TIME\"]\n", + " \n", + " - \"pattern\": [\"CREDIT_CARD\", \"SSN\", \"PHONE\", \"EMAIL\"]\n", + " \n", + " - \"flair\": [ \"LOCATION\",\n", + " \"PERSON\",\n", + " \"NRP\",\n", + " \"GPE\",\n", + " \"ORGANIZATION\",\n", + " \"MAC_ADDRESS\",\n", + " \"US_BANK_NUMBER\",\n", + " \"IMEI\",\n", + " \"TITLE\",\n", + " \"LICENSE_PLATE\",\n", + " \"US_PASSPORT\",\n", + " \"CURRENCY\",\n", + " \"ROUTING_NUMBER\",\n", + " \"US_ITIN\",\n", + " \"US_BANK_NUMBER\",\n", + " \"US_DRIVER_LICENSE\",\n", + " \"AGE\",\n", + " \"PASSWORD\",\n", + " \"SWIFT_CODE\"\n", + " ]\n", + " \n", + "* **score_threshold**:\n", + " \n", + " Minimum confidence value, the default is 0 to align with presidio.AnalyzerEngine\n", + " \n", + "* **generate_json_rpt**:\n", + "\n", + " Whether to generate the json report of the explaination\n", + " \n", + "* **generate_html_rpt**:\n", + "\n", + " Whether to generate the html with highlighted pii entities or not\n", + " \n", + "* **is_full_text**:\n", + "\n", + " Whether to return the full text or just the sentences with pii entities.\n", + " \n", + "* **is_full_html**: `bool`\n", + " \n", + " Whether to return the full html or just the annotated html\n", + " \n", + "* **is_full_report**: `bool`\n", + " \n", + " Whether to return the full json report or just the score and start, end index\n", + " \n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "83f616d2", + "metadata": {}, + "source": [ + "### 1.2. Outputs:\n", + "\n", + "There are two outputs of this function. \n", + "\n", + "* **output_path**: `str`\n", + " \n", + " The directory stored all the anonymized text files\n", + "\n", + "* **rpt_json**: `dict`\n", + "\n", + " A dict of reporting to explain how does the model detect the pii entity\n", + " \n", + "* **errors** : `dict`\n", + " A dict of errors when processing the text files if any\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "310de23a", + "metadata": {}, + "source": [ + "\n", + "## 2. Results\n", + "\n", + "The result of the function looks like the following: \n", + "\n", + "For example if the input string is \n", + "\n", + "`John Doe 's ssn is 182838483, connect john doe with john_doe@gmail.com or 6288389029, he can pay you with 41482929939393`\n", + "\n", + "The anonymized_text is \n", + "\n", + "`'s is , connect with or , he can pay you with `\n", + "\n", + "The html_str is\n", + "\n", + "

John Doe'sPERSON ssnORGANIZATION is 182838483SSN, connect me with john_doe@gmail.comPERSONjohn_doe@gmail.comEMAIL or 6288389029PHONE, he can pay you with 41482929939393CREDIT_CARD\n", + "

\n", + "\n", + "The json report that explain the output is\n", + "\n", + "```yaml\n", + "\n", + "[\n", + " {\n", + " \"entity_type\": \"PERSON\", # result of the labeling\n", + " \"start\": 0, # start positon of the entity\n", + " \"end\": 9, # end postion of the entity\n", + " \"score\": 0.99, # the confident score of the model + context_improvement\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\", # which recognizer is used to recognize this entity\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 0.99, # The original confident score from the pre-trained model\n", + " \"score\": 0.99, # the final score = original_score + score_context_improvement\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0, # The improvement from the context\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_5577088640\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " ....\n", + "]\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "ce2199fb", + "metadata": {}, + "source": [ + "\n", + "## 3. End-to-end Demo\n" + ] + }, + { + "cell_type": "markdown", + "id": "fc42debf-f363-48f9-9512-3951d352fb1d", + "metadata": {}, + "source": [ + "### 3.1. Recognition configurations \n", + " - model: which model you want to use.\n", + " - entities: What entities to recognize? \n", + " - score_threshold: From which score to mark the recogniztion as trusted?\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "2a290d0f-15da-434d-b3fc-46ebb35be611", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:17:04,305 [info] Project loaded successfully: {'project_name': 'pii'}\n", + "> 2023-07-31 02:17:04,312 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}\n", + "> 2023-07-31 02:17:04,408 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}\n", + "> 2023-07-31 02:17:04,409 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '51b5ad8144004e52a1008c08850842c8', 'db': None}\n", + "2023-07-31 02:17:04,567 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin\n", + "2023-07-31 02:17:07,730 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP\n", + "Model loaded\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fd09fd6ee2844e13b5839e1fd20ef222", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00\n", + ".dictlist {\n", + " background-color: #4EC64B;\n", + " text-align: center;\n", + " margin: 4px;\n", + " border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;}\n", + ".artifact {\n", + " cursor: pointer;\n", + " background-color: #4EC64B;\n", + " text-align: left;\n", + " margin: 4px; border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;\n", + "}\n", + "div.block.hidden {\n", + " display: none;\n", + "}\n", + ".clickable {\n", + " cursor: pointer;\n", + "}\n", + ".ellipsis {\n", + " display: inline-block;\n", + " max-width: 60px;\n", + " white-space: nowrap;\n", + " overflow: hidden;\n", + " text-overflow: ellipsis;\n", + "}\n", + ".master-wrapper {\n", + " display: flex;\n", + " flex-flow: row nowrap;\n", + " justify-content: flex-start;\n", + " align-items: stretch;\n", + "}\n", + ".master-tbl {\n", + " flex: 3\n", + "}\n", + ".master-wrapper > div {\n", + " margin: 4px;\n", + " padding: 10px;\n", + "}\n", + "iframe.fileview {\n", + " border: 0 none;\n", + " height: 100%;\n", + " width: 100%;\n", + " white-space: pre-wrap;\n", + "}\n", + ".pane-header-title {\n", + " width: 80%;\n", + " font-weight: 500;\n", + "}\n", + ".pane-header {\n", + " line-height: 1;\n", + " background-color: #4EC64B;\n", + " padding: 3px;\n", + "}\n", + ".pane-header .close {\n", + " font-size: 20px;\n", + " font-weight: 700;\n", + " float: right;\n", + " margin-top: -5px;\n", + "}\n", + ".master-wrapper .right-pane {\n", + " border: 1px inset silver;\n", + " width: 40%;\n", + " min-height: 300px;\n", + " flex: 3\n", + " min-width: 500px;\n", + "}\n", + ".master-wrapper * {\n", + " box-sizing: border-box;\n", + "}\n", + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
pii0Jul 31 02:17:04completedpii-recognizer-recognize-pii
v3io_user=pengw
kind=
owner=pengw
host=jupyter-pengw-5f99fb678d-mnvxl
model=whole
input_path=./data/
output_path=./data/output1/
entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
output_suffix=output
html_key=highlighted
score_threshold=0.5
highlighted
output_path
rpt_json
errors
\n", + "
\n", + "
\n", + "
\n", + " Title\n", + " ×\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:17:12,403 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}\n" + ] + } + ], + "source": [ + "import mlrun\n", + "artifact_path = \"./\"\n", + "proj = mlrun.get_or_create_project(\"pii\", \"./\")\n", + "fn = mlrun.code_to_function(\n", + " project=\"pii\",\n", + " name=\"pii_recognizer\",\n", + " filename=\"pii_recognizer.py\",\n", + " kind=\"job\",\n", + " image=\"mlrun/mlrun\",\n", + " handler=\"recognize_pii\",\n", + " description=\"This function is used to recognize PII in a given text\",\n", + ")\n", + "run_obj = fn.run(\n", + " artifact_path = artifact_path,\n", + " params= {\n", + " 'model': \"whole\", \n", + " 'input_path': \"./data/\",\n", + " 'output_path': \"./data/output1/\",\n", + " \"entities\": ['PERSON', \"EMAIL\", \"PHONE\", \"LOCATION\", \"ORGANIZATION\"], # the entities that needs to recognize\n", + " \"output_suffix\": \"output\",\n", + " \"html_key\": \"highlighted\",\n", + " \"score_threshold\" : 0.5, # the score threshold to mark the recognition as trusted\n", + " },\n", + " returns = [\"output_path: path\", \"rpt_json: file\", \"errors: file\"],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "38e1a44b-e045-4c50-a40f-fbc7e77d6c6b", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c23dc77030224dfc825d7da86c6c1220", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00,\n", + "\n", + "We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of . Your flight tickets have been booked, and you will be departing on July 15th, 2023.\n", + "\n", + "Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations.\n", + "\n", + "We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team.\n", + "\n", + " is 182838483, connect him with or , he can pay you with 9393\n" + ] + } + ], + "source": [ + "#get the mlrun context\n", + "context = mlrun.get_or_create_ctx('pii_ctx1')\n", + "import pathlib\n", + "from tqdm.auto import tqdm\n", + "for i, txt_file in enumerate(\n", + " tqdm(\n", + " list(pathlib.Path(\"./data/output1/\").glob(\"*.txt\")),\n", + " desc=\"Processing files\",\n", + " unit=\"file\",\n", + " )\n", + " ):\n", + " # Load the str from the text file\n", + " text = txt_file.read_text()\n", + " print(text)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "72c31b9c-47cc-4e73-8c76-041f78cfd305", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Highlighted Pii Entities

Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON,\n", + "\n", + "We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION. Your flight tickets have been booked, and you will be departing on July 15th, 2023.\n", + "\n", + "Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations.\n", + "\n", + "We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team.\n", + "

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON@gmail.com or 6288389029PHONE, he can pay you with 4148292993PHONE9393

  • " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#check the highlighted html \n", + "html_output = context.get_cached_artifact(\"highlighted\")\n", + "html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode(\"utf-8\")\n", + "from IPython.core.display import display, HTML\n", + "display(HTML(html_str))" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "d4c7fb04-af53-4e63-8b0a-e14e1184f973", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"data/letter.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 9,\n", + " \"end\": 17,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139944219101744\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"LOCATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1.0,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1.0,\n", + " \"score\": 1.0,\n", + " \"textual_explanation\": \"Identified as LOC by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944219101936\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139944219101744\"\n", + " }\n", + " }\n", + " ],\n", + " \"data/pii_data.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 0,\n", + " \"end\": 12,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139944219101744\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 13,\n", + " \"end\": 16,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139944219101744\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 53,\n", + " \"end\": 58,\n", + " \"score\": 1.0,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1.0,\n", + " \"score\": 1.0,\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944219101936\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 48,\n", + " \"end\": 52,\n", + " \"score\": 0.87,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 0.87,\n", + " \"score\": 0.87,\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944219101936\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"EMAIL\",\n", + " \"start\": 48,\n", + " \"end\": 68,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"EMAIL\",\n", + " \"pattern\": \"\\\\S+@\\\\S+\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139944352474640\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 72,\n", + " \"end\": 82,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"PHONE\",\n", + " \"pattern\": \"\\\\(?\\\\d{3}\\\\)?[-.\\\\s]?\\\\d{3}[-.\\\\s]?\\\\d{4}\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139944352476560\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 104,\n", + " \"end\": 114,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"PHONE\",\n", + " \"pattern\": \"\\\\(?\\\\d{3}\\\\)?[-.\\\\s]?\\\\d{3}[-.\\\\s]?\\\\d{4}\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139944352476560\"\n", + " }\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "#check the json report about the explanation.\n", + "rpt_output1 = context.get_cached_artifact(\"rpt_json\")\n", + "rpt_str1 = mlrun.get_dataitem(rpt_output1.get_target_path()).get().decode(\"utf-8\")\n", + "import json\n", + "obj = json.loads(rpt_str1)\n", + " \n", + "# Pretty Print JSON\n", + "json_formatted_str1 = json.dumps(obj, indent=4)\n", + "print(json_formatted_str1)" + ] + }, + { + "cell_type": "markdown", + "id": "1182c119", + "metadata": {}, + "source": [ + "### 3.2. Masking configurations \n", + " - entity_operator_map: it defined what to do with recognized tokens? Mask them? mask them with what? remove them? replace them?\n", + "
    \n",
    +    "     entity_operator_map = {\n",
    +    "        \"PERSON\": (\"keep\", {}),\n",
    +    "        \"EMAIL\": (\"mask\", {\"masking_char\": \"😀\", \"chars_to_mask\": 5, \"from_end\": False}),\n",
    +    "        \"PHONE\": (\"hash\", {}),\n",
    +    "        \"LOCATION\": (\"redact\", {}),\n",
    +    "        \"ORGANIZATION\": (\"replace\", {\"new_value\": \"Company XYZ\"})\n",
    +    "        }\n",
    +    "     
    " + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "df325ea8-4b01-4485-b835-e0196ffe83d7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:20:40,550 [info] Project loaded successfully: {'project_name': 'pii'}\n", + "> 2023-07-31 02:20:40,556 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}\n", + "> 2023-07-31 02:20:40,649 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}\n", + "> 2023-07-31 02:20:40,649 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '2b43f80c7ca44b43b229760bb55f814d', 'db': None}\n", + "2023-07-31 02:20:40,812 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin\n", + "2023-07-31 02:20:44,130 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP\n", + "Model loaded\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5ad56413aad64e59b177666ca0a89a01", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00\n", + ".dictlist {\n", + " background-color: #4EC64B;\n", + " text-align: center;\n", + " margin: 4px;\n", + " border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;}\n", + ".artifact {\n", + " cursor: pointer;\n", + " background-color: #4EC64B;\n", + " text-align: left;\n", + " margin: 4px; border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;\n", + "}\n", + "div.block.hidden {\n", + " display: none;\n", + "}\n", + ".clickable {\n", + " cursor: pointer;\n", + "}\n", + ".ellipsis {\n", + " display: inline-block;\n", + " max-width: 60px;\n", + " white-space: nowrap;\n", + " overflow: hidden;\n", + " text-overflow: ellipsis;\n", + "}\n", + ".master-wrapper {\n", + " display: flex;\n", + " flex-flow: row nowrap;\n", + " justify-content: flex-start;\n", + " align-items: stretch;\n", + "}\n", + ".master-tbl {\n", + " flex: 3\n", + "}\n", + ".master-wrapper > div {\n", + " margin: 4px;\n", + " padding: 10px;\n", + "}\n", + "iframe.fileview {\n", + " border: 0 none;\n", + " height: 100%;\n", + " width: 100%;\n", + " white-space: pre-wrap;\n", + "}\n", + ".pane-header-title {\n", + " width: 80%;\n", + " font-weight: 500;\n", + "}\n", + ".pane-header {\n", + " line-height: 1;\n", + " background-color: #4EC64B;\n", + " padding: 3px;\n", + "}\n", + ".pane-header .close {\n", + " font-size: 20px;\n", + " font-weight: 700;\n", + " float: right;\n", + " margin-top: -5px;\n", + "}\n", + ".master-wrapper .right-pane {\n", + " border: 1px inset silver;\n", + " width: 40%;\n", + " min-height: 300px;\n", + " flex: 3\n", + " min-width: 500px;\n", + "}\n", + ".master-wrapper * {\n", + " box-sizing: border-box;\n", + "}\n", + "\n", + "
    \n", + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    pii0Jul 31 02:20:40completedpii-recognizer-recognize-pii
    v3io_user=pengw
    kind=
    owner=pengw
    host=jupyter-pengw-5f99fb678d-mnvxl
    model=whole
    input_path=./data/
    output_path=./data/output2/
    entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
    output_suffix=output
    html_key=highlighted
    score_threshold=0.5
    entity_operator_map={'PERSON': ('keep', {}), 'EMAIL': ('mask', {'masking_char': '😀', 'chars_to_mask': 100, 'from_end': False, 'entity_type': 'EMAIL'}), 'PHONE': ('hash', {}), 'LOCATION': ('redact', {}), 'ORGANIZATION': ('replace', {'new_value': 'Company XYZ', 'entity_type': 'ORGANIZATION'})}
    highlighted
    output_path
    rpt_json
    errors
    \n", + "
    \n", + "
    \n", + "
    \n", + " Title\n", + " ×\n", + "
    \n", + " \n", + "
    \n", + "
    \n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:20:48,903 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}\n" + ] + } + ], + "source": [ + "import mlrun\n", + "artifact_path = \"./\"\n", + "proj = mlrun.get_or_create_project(\"pii\", \"./\")\n", + "fn = mlrun.code_to_function(\n", + " project=\"pii\",\n", + " name=\"pii_recognizer\",\n", + " filename=\"pii_recognizer.py\",\n", + " kind=\"job\",\n", + " image=\"mlrun/mlrun\",\n", + " handler=\"recognize_pii\",\n", + " description=\"This function is used to recognize PII in a given text\",\n", + ")\n", + "\n", + "entity_operator_map = {\n", + " \"PERSON\": (\"keep\", {}),\n", + " \"EMAIL\": (\"mask\", {\"masking_char\": \"😀\", \"chars_to_mask\" : 100, \"from_end\": False}),\n", + " \"PHONE\": (\"hash\", {}),\n", + " \"LOCATION\": (\"redact\", {}),\n", + " \"ORGANIZATION\": (\"replace\", {\"new_value\": \"Company XYZ\"})\n", + " }\n", + "run_obj = fn.run(\n", + " artifact_path = artifact_path,\n", + " params= {\n", + " 'model': \"whole\", \n", + " 'input_path': \"./data/\",\n", + " 'output_path': \"./data/output2/\",\n", + " \"entities\": ['PERSON', \"EMAIL\", \"PHONE\", \"LOCATION\", \"ORGANIZATION\"],\n", + " \"output_suffix\": \"output\",\n", + " \"html_key\": \"highlighted\",\n", + " \"score_threshold\" : 0.5,\n", + " \"entity_operator_map\": entity_operator_map,\n", + " \n", + " },\n", + " returns = [\"output_path: path\", \"rpt_json: file\", \"errors: file\"],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "2583e72b-8dda-4469-8e2b-f492851015af", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "552ad96fd23e497ea6e547936c7853a0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00Highlighted Pii Entities

    Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON,\n", + "\n", + "We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION. Your flight tickets have been booked, and you will be departing on July 15th, 2023.\n", + "\n", + "Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations.\n", + "\n", + "We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team.\n", + "

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON@gmail.com or 6288389029PHONE, he can pay you with 4148292993PHONE9393

  • " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#check the highlighted html \n", + "html_output = context.get_cached_artifact(\"highlighted\")\n", + "html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode(\"utf-8\")\n", + "from IPython.core.display import display, HTML\n", + "display(HTML(html_str))" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "3a087fb1-dde7-4ba9-9f53-a10f9099c769", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"data/letter.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 9,\n", + " \"end\": 17,\n", + " \"score\": 1.0,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1.0,\n", + " \"score\": 1.0,\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944345555488\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"LOCATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1.0,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1.0,\n", + " \"score\": 1.0,\n", + " \"textual_explanation\": \"Identified as LOC by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944345555488\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139943499301312\"\n", + " }\n", + " }\n", + " ],\n", + " \"data/pii_data.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 0,\n", + " \"end\": 12,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139943499301312\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 13,\n", + " \"end\": 16,\n", + " \"score\": 1,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"CustomSpacyRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1,\n", + " \"score\": 1,\n", + " \"textual_explanation\": \"Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"CustomSpacyRecognizer\",\n", + " \"recognizer_identifier\": \"CustomSpacyRecognizer_139943499301312\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 53,\n", + " \"end\": 58,\n", + " \"score\": 1.0,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 1.0,\n", + " \"score\": 1.0,\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944345555488\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 48,\n", + " \"end\": 52,\n", + " \"score\": 0.87,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"FlairRecognizer\",\n", + " \"pattern_name\": null,\n", + " \"pattern\": null,\n", + " \"original_score\": 0.87,\n", + " \"score\": 0.87,\n", + " \"textual_explanation\": \"Identified as PER by Flair's Named Entity Recognition\",\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_identifier\": \"Flair Analytics_139944345555488\",\n", + " \"recognizer_name\": \"Flair Analytics\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"EMAIL\",\n", + " \"start\": 48,\n", + " \"end\": 68,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"EMAIL\",\n", + " \"pattern\": \"\\\\S+@\\\\S+\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139943864893792\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 72,\n", + " \"end\": 82,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"PHONE\",\n", + " \"pattern\": \"\\\\(?\\\\d{3}\\\\)?[-.\\\\s]?\\\\d{3}[-.\\\\s]?\\\\d{4}\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139943864894128\"\n", + " }\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 104,\n", + " \"end\": 114,\n", + " \"score\": 0.5,\n", + " \"analysis_explanation\": {\n", + " \"recognizer\": \"PatternRecognizer\",\n", + " \"pattern_name\": \"PHONE\",\n", + " \"pattern\": \"\\\\(?\\\\d{3}\\\\)?[-.\\\\s]?\\\\d{3}[-.\\\\s]?\\\\d{4}\",\n", + " \"original_score\": 0.5,\n", + " \"score\": 0.5,\n", + " \"textual_explanation\": null,\n", + " \"score_context_improvement\": 0,\n", + " \"supportive_context_word\": \"\",\n", + " \"validation_result\": null\n", + " },\n", + " \"recognition_metadata\": {\n", + " \"recognizer_name\": \"PatternRecognizer\",\n", + " \"recognizer_identifier\": \"PatternRecognizer_139943864894128\"\n", + " }\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "#check the json report about the explanation.\n", + "rpt_output1 = context.get_cached_artifact(\"rpt_json\")\n", + "rpt_str1 = mlrun.get_dataitem(rpt_output1.get_target_path()).get().decode(\"utf-8\")\n", + "import json\n", + "obj = json.loads(rpt_str1)\n", + " \n", + "# Pretty Print JSON\n", + "json_formatted_str1 = json.dumps(obj, indent=4)\n", + "print(json_formatted_str1)" + ] + }, + { + "cell_type": "markdown", + "id": "7c058fe3-000c-4566-a11e-80283426d945", + "metadata": {}, + "source": [ + "### 3.3 Output configurations \n", + " - is_full_text: whether produce full text or just the sentences have PII entities in it\n", + " - generate_html: whether to produce the html with highlighted pii entities\n", + " - generate_json: whether to proudce the json report with the explaination of the process\n", + " - is_full_html: whether produce full text with the pii entities highlighted or just sentences with pii entities.\n", + " - is_full_report: whether produce the json report with detailed information or just start, end index and scores." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "6a684769", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:22:57,789 [info] Project loaded successfully: {'project_name': 'pii'}\n", + "> 2023-07-31 02:22:57,799 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}\n", + "> 2023-07-31 02:22:57,891 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}\n", + "> 2023-07-31 02:22:57,892 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '3f6d701e423346b39026dc365698c15c', 'db': None}\n", + "2023-07-31 02:22:58,079 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin\n", + "2023-07-31 02:23:01,565 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP\n", + "Model loaded\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ad05f59e8c604629a01f797dc84ec530", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00\n", + ".dictlist {\n", + " background-color: #4EC64B;\n", + " text-align: center;\n", + " margin: 4px;\n", + " border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;}\n", + ".artifact {\n", + " cursor: pointer;\n", + " background-color: #4EC64B;\n", + " text-align: left;\n", + " margin: 4px; border-radius: 3px; padding: 0px 3px 1px 3px; display: inline-block;\n", + "}\n", + "div.block.hidden {\n", + " display: none;\n", + "}\n", + ".clickable {\n", + " cursor: pointer;\n", + "}\n", + ".ellipsis {\n", + " display: inline-block;\n", + " max-width: 60px;\n", + " white-space: nowrap;\n", + " overflow: hidden;\n", + " text-overflow: ellipsis;\n", + "}\n", + ".master-wrapper {\n", + " display: flex;\n", + " flex-flow: row nowrap;\n", + " justify-content: flex-start;\n", + " align-items: stretch;\n", + "}\n", + ".master-tbl {\n", + " flex: 3\n", + "}\n", + ".master-wrapper > div {\n", + " margin: 4px;\n", + " padding: 10px;\n", + "}\n", + "iframe.fileview {\n", + " border: 0 none;\n", + " height: 100%;\n", + " width: 100%;\n", + " white-space: pre-wrap;\n", + "}\n", + ".pane-header-title {\n", + " width: 80%;\n", + " font-weight: 500;\n", + "}\n", + ".pane-header {\n", + " line-height: 1;\n", + " background-color: #4EC64B;\n", + " padding: 3px;\n", + "}\n", + ".pane-header .close {\n", + " font-size: 20px;\n", + " font-weight: 700;\n", + " float: right;\n", + " margin-top: -5px;\n", + "}\n", + ".master-wrapper .right-pane {\n", + " border: 1px inset silver;\n", + " width: 40%;\n", + " min-height: 300px;\n", + " flex: 3\n", + " min-width: 500px;\n", + "}\n", + ".master-wrapper * {\n", + " box-sizing: border-box;\n", + "}\n", + "\n", + "
    \n", + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    pii0Jul 31 02:22:57completedpii-recognizer-recognize-pii
    v3io_user=pengw
    kind=
    owner=pengw
    host=jupyter-pengw-5f99fb678d-mnvxl
    model=whole
    input_path=./data/
    output_path=./data/output3/
    entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
    output_suffix=output
    html_key=highlighted
    score_threshold=0.5
    entity_operator_map={'PERSON': ('keep', {}), 'EMAIL': ('mask', {'masking_char': '😀', 'chars_to_mask': 100, 'from_end': False, 'entity_type': 'EMAIL'}), 'PHONE': ('hash', {}), 'LOCATION': ('redact', {}), 'ORGANIZATION': ('replace', {'new_value': 'Company XYZ', 'entity_type': 'ORGANIZATION'})}
    is_full_text=False
    is_full_html=False
    is_full_report=False
    highlighted
    output_path
    rpt_json
    errors
    \n", + "
    \n", + "
    \n", + "
    \n", + " Title\n", + " ×\n", + "
    \n", + " \n", + "
    \n", + "
    \n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-31 02:23:06,096 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}\n" + ] + } + ], + "source": [ + "import mlrun\n", + "artifact_path = \"./\"\n", + "proj = mlrun.get_or_create_project(\"pii\", \"./\")\n", + "fn = mlrun.code_to_function(\n", + " project=\"pii\",\n", + " name=\"pii_recognizer\",\n", + " filename=\"pii_recognizer.py\",\n", + " kind=\"job\",\n", + " image=\"mlrun/mlrun\",\n", + " handler=\"recognize_pii\",\n", + " description=\"This function is used to recognize PII in a given text\",\n", + ")\n", + "\n", + "entity_operator_map = {\n", + " \"PERSON\": (\"keep\", {}),\n", + " \"EMAIL\": (\"mask\", {\"masking_char\": \"😀\", \"chars_to_mask\" : 100, \"from_end\": False}),\n", + " \"PHONE\": (\"hash\", {}),\n", + " \"LOCATION\": (\"redact\", {}),\n", + " \"ORGANIZATION\": (\"replace\", {\"new_value\": \"Company XYZ\"})\n", + " }\n", + "run_obj = fn.run(\n", + " artifact_path = artifact_path,\n", + " params= {\n", + " 'model': \"whole\", \n", + " 'input_path': \"./data/\",\n", + " 'output_path': \"./data/output3/\",\n", + " \"entities\": ['PERSON', \"EMAIL\", \"PHONE\", \"LOCATION\", \"ORGANIZATION\"],\n", + " \"output_suffix\": \"output\",\n", + " \"html_key\": \"highlighted\",\n", + " \"score_threshold\" : 0.5,\n", + " \"entity_operator_map\": entity_operator_map,\n", + " \"is_full_text\": False,\n", + " \"is_full_html\": False,\n", + " \"is_full_report\": False,\n", + " },\n", + " returns = [\"output_path: path\", \"rpt_json: file\", \"errors: file\"],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "0e10d8fa", + "metadata": {}, + "outputs": [], + "source": [ + "#get the mlrun context\n", + "context = mlrun.get_or_create_ctx('pii_ctx')\n", + "import pathlib\n", + "from tqdm.auto import tqdm" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "fb303fef", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f478b2a3792e42beabad632b9523e169", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Processing files: 0%| | 0/2 [00:00Highlighted Pii Entities

    Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON,\n", + "\n", + "We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON

  • " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#check the highlighted html \n", + "html_output = context.get_cached_artifact(\"highlighted\")\n", + "html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode(\"utf-8\")\n", + "from IPython.core.display import display, HTML\n", + "display(HTML(html_str))" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "26f9e706", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"data/letter.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 9,\n", + " \"end\": 17,\n", + " \"score\": 1\n", + " },\n", + " {\n", + " \"entity_type\": \"LOCATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1.0\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 248,\n", + " \"end\": 255,\n", + " \"score\": 1\n", + " }\n", + " ],\n", + " \"data/pii_data.txt\": [\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 0,\n", + " \"end\": 12,\n", + " \"score\": 1\n", + " },\n", + " {\n", + " \"entity_type\": \"ORGANIZATION\",\n", + " \"start\": 13,\n", + " \"end\": 16,\n", + " \"score\": 1\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 53,\n", + " \"end\": 58,\n", + " \"score\": 1.0\n", + " },\n", + " {\n", + " \"entity_type\": \"PERSON\",\n", + " \"start\": 48,\n", + " \"end\": 52,\n", + " \"score\": 0.87\n", + " },\n", + " {\n", + " \"entity_type\": \"EMAIL\",\n", + " \"start\": 48,\n", + " \"end\": 68,\n", + " \"score\": 0.5\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 72,\n", + " \"end\": 82,\n", + " \"score\": 0.5\n", + " },\n", + " {\n", + " \"entity_type\": \"PHONE\",\n", + " \"start\": 104,\n", + " \"end\": 114,\n", + " \"score\": 0.5\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "#check the json report about the explanation.\n", + "rpt_output = context.get_cached_artifact(\"rpt_json\")\n", + "rpt_str = mlrun.get_dataitem(rpt_output.get_target_path()).get().decode(\"utf-8\")\n", + "import json\n", + "obj = json.loads(rpt_str)\n", + " \n", + "# Pretty Print JSON\n", + "json_formatted_str = json.dumps(obj, indent=4)\n", + "print(json_formatted_str)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pii", + "language": "python", + "name": "conda-env-.conda-pii-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.py b/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.py new file mode 100644 index 00000000..0acc55dc --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/pii_recognizer.py @@ -0,0 +1,951 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +import pathlib +import tempfile +import warnings +from typing import List, Set, Tuple, Union + +import annotated_text.util as at_util +import mlrun +import nltk +import pandas as pd +import presidio_analyzer as pa +import presidio_anonymizer as pre_anoymizer +from presidio_anonymizer.entities import OperatorConfig +from tqdm import tqdm + +try: + import flair as fl +except ModuleNotFoundError: + print("Flair is not installed") + +# There is a conflict between Rust-based tokenizers' parallel processing +# and Python's fork operations during multiprocessing. To avoid this, we need +# the following two lines + +os.environ["TOKENIZERS_PARALLELISM"] = "false" +warnings.filterwarnings("ignore") + +logger = logging.getLogger("pii-recognizer") + + +# Add the constant classes of Models and Entities to govern the whole package +class Models: + WHOLE = "whole" + PATTERN = "pattern" + SPACY = "spacy" + FLAIR = "flair" + + +class Entities: + CREDIT_CARD = "CREDIT_CARD" + SSN = "SSN" + PHONE = "PHONE" + EMAIL = "EMAIL" + LOCATION = "LOCATION" + PERSON = "PERSON" + NRP = "NRP" + ORGANIZATION = "ORGANIZATION" + DATE_TIME = "DATE_TIME" + GPE = ("GPE",) + MAC_ADDRESS = "MAC_ADDRESS" + US_BANK_NUMBER = "US_BANK_NUMBER" + IMEI = "IMEI" + TITLE = "TITLE" + LICENSE_PLATE = "LICENSE_PLATE" + US_PASSPORT = "US_PASSPORT" + CURRENCY = "CURRENCY" + ROUTING_NUMBER = "ROUTING_NUMBER" + US_ITIN = "US_ITIN" + US_BANK_NUMBER = "US_BANK_NUMBER" + US_DRIVER_LICENSE = "US_DRIVER_LICENSE" + AGE = "AGE" + PASSWORD = "PASSWORD" + SWIFT_CODE = "SWIFT_CODE" + + +class PatternRecognizerFactory: + """ + Factory for creating pattern recognizers, it can be extended in the future to + add more regex pattern for different entities. For the pattern recognizer to work, + we need construct a list of regex patterns for each entity. + """ + + RECOGNIZABLE_ENTITIES = { + "CREDIT_CARD": [pa.Pattern("CREDIT_CARD", r"\b(?:\d[ -]*?){13,16}\b", 0.5)], + "SSN": [pa.Pattern("SSN", r"\b\d{3}-?\d{2}-?\d{4}\b", 0.5)], + "PHONE": [pa.Pattern("PHONE", r"\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", 0.5)], + "EMAIL": [pa.Pattern("EMAIL", r"\S+@\S+", 0.5)], + } + + # create a list of pattern recognizers + @classmethod + def _create_pattern_recognizer(cls): + """ + For each entity, create a list of patterns to recognize it + + :param cls: PatternRecognizerFactory class + + :returns: List of pattern recognizers + """ + + # Entities to recognize and their regex patterns + + return [ + pa.PatternRecognizer(supported_entity=entity, patterns=pattern) + for entity, pattern in cls.RECOGNIZABLE_ENTITIES.items() + ] + + +class CustomSpacyRecognizer(pa.LocalRecognizer): + """ + Custom Spacy Recognizer from Presidio Analyzer trained on Privy data. + The privy data is generated using this https://github.com/pixie-io/pixie/tree/main/src/datagen/pii/privy + It can be used to recognize custom entities, Since we want to use Presidio's Registries to generate AnalyzerEngine, + it inherits from Presidio Analyzer's LocalRecognizer class. + """ + + # Entities to recognize + + RECOGNIZABLE_ENTITIES = { + "LOCATION", + "PERSON", + "NRP", + "ORGANIZATION", + "DATE_TIME", + } + + # Default explanation for this recognizer + + _DEFAULT_EXPLANATION = ( + "Identified as {} by Spacy's Named Entity Recognition (Privy-trained)" + ) + + # Label groups to check + + _DEFAULT_CHECK_LABEL_GROUPS = [ + ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}), + ({"PERSON"}, {"PER", "PERSON"}), + ({"NRP"}, {"NORP", "NRP"}), + ({"ORGANIZATION"}, {"ORG"}), + ({"DATE_TIME"}, {"DATE_TIME"}), + ] + + # pretrained model for this recognizer + + _DEFAULT_MODEL_LANGUAGES = { + "en": "beki/en_spacy_pii_distilbert", + } + + _DEFAULT_PRESIDIO_EQUIVALENCES = { + "PER": "PERSON", + "LOC": "LOCATION", + "ORG": "ORGANIZATION", + "NROP": "NRP", + "DATE_TIME": "DATE_TIME", + } + + def __init__( + self, + supported_language: str = "en", + supported_entities: List[str] = None, + check_label_groups: Tuple[Set, Set] = None, + context: List[str] = None, + ner_strength: float = 1, + ): + """ + Initialize Spacy Recognizer. + + :param supported_language: Language to use, default is English + :param supported_entities: Entities to use for recognition + :param check_label_groups: Label groups to check for the entities + :param context: Context to use if any + :param ner_strength: Default confidence for NER prediction + + :returns: SpacyRecognizer object + """ + + # Default confidence for NER prediction + self.ner_strength = ner_strength + + self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS + supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES + super().__init__( + supported_entities=supported_entities, + supported_language=supported_language, + ) + + # get the presidio explanation for the result + + def _build_spacy_explanation( + self, original_score: float, explanation: str + ) -> pa.AnalysisExplanation: + """ + Create explanation for why this result was detected. + + :param original_score: Score given by this recognizer + :param explanation: Explanation string + + :returns: Presidio AnalysisExplanation object + """ + explanation = pa.AnalysisExplanation( + recognizer=self.__class__.__name__, + original_score=original_score, + textual_explanation=explanation, + ) + return explanation + + # main method for the recognizer + def analyze(self, text: str, entities: List[str], nlp_artifacts=None): # noqa D102 + """ + Analyze text using Spacy. + + :param text: Text to analyze + :param entities: Entities to analyze + :param nlp_artifacts: NLP artifacts to use + + :returns: List of Presidio RecognizerResult objects + """ + results = [] + if not nlp_artifacts: + logger.warning("Skipping SpaCy, nlp artifacts not provided...") + return results + + ner_entities = nlp_artifacts.entities + + # recognize the supported entities + for entity in entities: + if entity not in self.supported_entities: + continue + for ent in ner_entities: + if not self.__check_label(entity, ent.label_, self.check_label_groups): + continue + + # string of the explanation saying the entity is recognized by spacy + textual_explanation = self._DEFAULT_EXPLANATION.format(ent.label_) + explanation = self._build_spacy_explanation( + self.ner_strength, textual_explanation + ) + + # create the standard result with the entity, start, end, score, and explanation + spacy_result = pa.RecognizerResult( + entity_type=entity, + start=ent.start_char, + end=ent.end_char, + score=self.ner_strength, + analysis_explanation=explanation, + recognition_metadata={ + pa.RecognizerResult.RECOGNIZER_NAME_KEY: self.name + }, + ) + results.append(spacy_result) + + return results + + @staticmethod + def __check_label( + entity: str, label: str, check_label_groups: Tuple[Set, Set] + ) -> bool: + """ + Check if the label is in the label group. + + :param entity: Entity to check + :param label: Label to check + :param check_label_groups: Label groups to check + + :returns: True if the label is in the label group, False otherwise + """ + return any( + entity in egrp and label in lgrp for egrp, lgrp in check_label_groups + ) + + +# Class to use Flair with Presidio as an external recognizer. +class FlairRecognizer(pa.EntityRecognizer): + """ + Wrapper for a flair model, if needed to be used within Presidio Analyzer. + This is to make sure the recognizer can be registered with Presidio registry. + """ + + RECOGNIZABLE_ENTITIES = { + "LOCATION", + "PERSON", + "NRP", + "GPE", + "ORGANIZATION", + "MAC_ADDRESS", + "US_BANK_NUMBER", + "IMEI", + "TITLE", + "LICENSE_PLATE", + "US_PASSPORT", + "CURRENCY", + "ROUTING_NUMBER", + "US_ITIN", + "US_BANK_NUMBER", + "US_DRIVER_LICENSE", + "AGE", + "PASSWORD", + "SWIFT_CODE", + } + + # This is used to construct the explanation for the result + + _DEFAULT_EXPLANATION = "Identified as {} by Flair's Named Entity Recognition" + + _DEFAULT_CHECK_LABEL_GROUPS = [ + ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}), + ({"PERSON"}, {"PER", "PERSON"}), + ({"NRP"}, {"NORP", "NRP"}), + ({"GPE"}, {"GPE"}), + ({"ORGANIZATION"}, {"ORG"}), + ({"MAC_ADDRESS"}, {"MAC_ADDRESS"}), + ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}), + ({"IMEI"}, {"IMEI"}), + ({"TITLE"}, {"TITLE"}), + ({"LICENSE_PLATE"}, {"LICENSE_PLATE"}), + ({"US_PASSPORT"}, {"US_PASSPORT"}), + ({"CURRENCY"}, {"CURRENCY"}), + ({"ROUTING_NUMBER"}, {"ROUTING_NUMBER"}), + ({"AGE"}, {"AGE"}), + ({"CURRENCY"}, {"CURRENCY"}), + ({"SWIFT_CODE"}, {"SWIFT_CODE"}), + ({"US_ITIN"}, {"US_ITIN"}), + ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}), + ({"US_DRIVER_LICENSE"}, {"US_DRIVER_LICENSE"}), + ] + + _DEFAULT_MODEL_LANGUAGES = { + "en": "beki/flair-pii-distilbert", + } + + _DEFAULT_PRESIDIO_EQUIVALENCES = { + "PER": "PERSON", + "LOC": "LOCATION", + "ORG": "ORGANIZATION", + "NROP": "NRP", + "URL": "URL", + "US_ITIN": "US_ITIN", + "US_PASSPORT": "US_PASSPORT", + "IBAN_CODE": "IBAN_CODE", + "IP_ADDRESS": "IP_ADDRESS", + "EMAIL_ADDRESS": "EMAIL", + "US_DRIVER_LICENSE": "US_DRIVER_LICENSE", + "US_BANK_NUMBER": "US_BANK_NUMBER", + } + + def __init__( + self, + supported_language: str = "en", + supported_entities: List[str] = None, + check_label_groups: Tuple[Set, Set] = None, + ): + """ + Initialize the FlairRecognizer. + + :param supported_language: Language to use + :param supported_entities: Entities to use + :param check_label_groups: Label groups to check + + :returns: FlairRecognizer object + + """ + self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS + + supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES + self.model = fl.models.SequenceTagger.load( + self._DEFAULT_MODEL_LANGUAGES.get(supported_language) + ) + + super().__init__( + supported_entities=supported_entities, + supported_language=supported_language, + name="Flair Analytics", + ) + + # main method for the recognizer + def analyze( + self, + text: str, + entities: List[str], + nlp_artifacts: pa.nlp_engine.NlpArtifacts = None, + ) -> List[pa.RecognizerResult]: + """ + Analyze text and return the results. + + :param text: The text for analysis. + :param entities: The list of entities to recognize. + :param nlp_artifacts: Not used by this recognizer but needed for the interface. + + :returns: The list of Presidio RecognizerResult constructed from the recognized Flair detections. + """ + + results = [] + + sentences = fl.data.Sentence(text) + self.model.predict(sentences) + + # If there are no specific list of entities, we will look for all of it. + if not entities: + entities = self.supported_entities + + # Go over the entities and check if they are in the supported entities list. + for entity in entities: + if entity not in self.supported_entities: + continue + + # Go over the sentences and check if the entity is in the sentence. + for ent in sentences.get_spans("ner"): + if not self.__check_label( + entity, ent.labels[0].value, self.check_label_groups + ): + continue + + # If the entity is in the sentence, we will add it to the results. + textual_explanation = self._DEFAULT_EXPLANATION.format( + ent.labels[0].value + ) + + # Build the explanation for the result + explanation = self._build_flair_explanation( + round(ent.score, 2), textual_explanation + ) + + flair_result = self._convert_to_recognizer_result(ent, explanation) + + results.append(flair_result) + + return results + + def _convert_to_recognizer_result( + self, entity: fl.data.Span, explanation: str + ) -> pa.RecognizerResult: + """ + Convert Flair result to Presidio RecognizerResult. + + :param entity: Flair entity of Span + :param explanation: Presidio AnalysisExplanation + + :returns: Presidio RecognizerResult + """ + + # Convert the entity type to Presidio entity type + entity_type = self._DEFAULT_PRESIDIO_EQUIVALENCES.get(entity.tag, entity.tag) + + # Convert the score to Presidio score + flair_score = round(entity.score, 2) + + # Create the Presidio RecognizerResult from the Flair entity + flair_results = pa.RecognizerResult( + entity_type=entity_type, + start=entity.start_position, + end=entity.end_position, + score=flair_score, + analysis_explanation=explanation, + ) + + return flair_results + + def _build_flair_explanation( + self, original_score: float, explanation: str + ) -> pa.AnalysisExplanation: + """ + Create explanation for why this result was detected. + + :param original_score: Score given by this recognizer + :param explanation: Explanation string + + :returns: Presidio AnalysisExplanation + """ + + # Create the Presidio AnalysisExplanation for the result + explanation = pa.AnalysisExplanation( + recognizer=self.__class__.__name__, + original_score=original_score, + textual_explanation=explanation, + ) + return explanation + + # sanity check of the entity and label before recognition + @staticmethod + def __check_label( + entity: str, label: str, check_label_groups: Tuple[Set, Set] + ) -> bool: + return any( + entity in egrp and label in lgrp for egrp, lgrp in check_label_groups + ) + + +# get the analyzer engine based on the model +def _get_analyzer_engine( + model: str = None, entities: List[str] = None +) -> pa.AnalyzerEngine: + """ + Return pa.AnalyzerEngine. + + :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole". + :param entities: The list of entities to use. + + :returns: pa.AnalyzerEngine + """ + # recognizer registry that can store multiple recognizers + registry = pa.RecognizerRegistry() + if model == Models.SPACY: + # custom spacy recognizer + spacy_recognizer = CustomSpacyRecognizer() + # add the custom build spacy recognizer + registry.add_recognizer(spacy_recognizer) + elif model == Models.FLAIR: + # pre-trained flair recognizer + flair_recognizer = FlairRecognizer() + # add the custom build flair recognizer + registry.add_recognizer(flair_recognizer) + elif model == Models.PATTERN: + # add the pattern recognizer + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + elif model == Models.WHOLE: + spacy_recognizer = CustomSpacyRecognizer() + flair_recognizer = FlairRecognizer() + registry.add_recognizer(spacy_recognizer) + registry.add_recognizer(flair_recognizer) + # add the pattern recognizer + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + elif not model and entities: + if set(entities) & CustomSpacyRecognizer.RECOGNIZABLE_ENTITIES: + spacy_recognizer = CustomSpacyRecognizer() + registry.add_recognizer(spacy_recognizer) + if set(entities) & FlairRecognizer.RECOGNIZABLE_ENTITIES: + flair_recognizer = FlairRecognizer() + registry.add_recognizer(flair_recognizer) + # add the pattern recognizer + if set(entities) & (set(PatternRecognizerFactory.RECOGNIZABLE_ENTITIES.keys())): + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + else: + raise ValueError( + f"argument of model and entities can not be None at the same time" + ) + analyzer = pa.AnalyzerEngine( + registry=registry, + supported_languages=["en"], + ) + + supported_entities = analyzer.get_supported_entities() + + if entities and not all(item in supported_entities for item in entities): + not_supported_entities = [ + item for item in entities if item not in supported_entities + ] + raise ValueError( + f"The current model {model} doesn't support the following entities: {not_supported_entities}. " + f"Supported entities are: {supported_entities}" + ) + return analyzer + + +def _get_anonymizer_engine() -> pre_anoymizer.AnonymizerEngine: + """ + Return AnonymizerEngine. + + :returns: The AnonymizerEngine. + """ + return pre_anoymizer.AnonymizerEngine() + + +def _anonymize( + text: str, + analyze_results: List[pa.RecognizerResult], + entity_operator_map: dict = None, + is_full_text: bool = True, +) -> str: + """ + Anonymize identified input using Presidio Abonymizer. + + :param text: The text for analysis. + :param analyze_results: The list of Presidio RecognizerResult constructed from + :param entity_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params. + :param is_full_text: Whether the text is full text or not. + + :returns: The anonymized text. + """ + if not text: + return "" + + anonymizer_engine = _get_anonymizer_engine() + if not entity_operator_map: + operators = None + else: + # Create OperatorConfig based on the entity_operator_map + operators = { + entity: OperatorConfig(operator_name, operator_params) + for entity, (operator_name, operator_params) in entity_operator_map.items() + } + + if is_full_text: + # Anonymize the entire text + return anonymizer_engine.anonymize( + text=text, analyzer_results=analyze_results, operators=operators + ).text + # Tokenize the text to sentences + sentences = nltk.sent_tokenize(text) + anonymized_sentences = [] + current_idx = 0 + + # Find the sentence that has pii entity + for sentence in sentences: + start_idx = current_idx + end_idx = start_idx + len(sentence) + + # Get the entities that are in the sentence, update hte start_idx and end_idx + sentence_results = [ + pa.RecognizerResult( + result.entity_type, + start=result.start - start_idx, + end=result.end - start_idx, + score=result.score, + ) + for result in analyze_results + if result.start >= start_idx and result.end <= end_idx + ] + + # If PII is detected + if sentence_results: + anonymized_sentence = anonymizer_engine.anonymize( + text=sentence, analyzer_results=sentence_results, operators=operators + ).text + anonymized_sentences.append(anonymized_sentence) + + current_idx = end_idx + + return " ".join(anonymized_sentences) + + +def _get_tokens( + text: str, analyze_results: List[pa.RecognizerResult], is_full: bool = True +) -> List[str]: + """ + Get the full tokens or only contains the entities that can form a sentence. + + :param text: The text for analysis. + :param analyze_results: The list of Presidio RecognizerResult constructed from + :param is_full: Whether return full tokens or just the tokens that only contains the entities that can form a sentence. + + :returns: The tokens. + """ + + tokens = [] + # sort by start index + results = sorted(analyze_results, key=lambda x: x.start) + for i, res in enumerate(results): + if i == 0: + tokens.append(text[: res.start]) + + # append entity text and entity type + tokens.append((text[res.start : res.end], res.entity_type)) + + # if another entity coming i.e. we're not at the last results element, + # add text up to next entity + if i != len(results) - 1: + tokens.append(text[res.end : results[i + 1].start]) + # if no more entities coming, add all remaining text + else: + tokens.append(text[res.end :]) + + # get the tokens that only contains the entities that can form a sentence + part_annontated_tokens = [] + if not is_full: + last_end_sentence = 0 + for i, token in enumerate(tokens): + if any(item in token for item in [".", "!", "?"]) and any( + type(item) is tuple for item in tokens[last_end_sentence:i] + ): + part_annontated_tokens.append(tokens[last_end_sentence:i]) + last_end_sentence = i + return part_annontated_tokens + return tokens + + +def _annotate( + text: str, st_analyze_results: List[pa.RecognizerResult], is_full_html: bool = True +) -> List[str]: + """ + Annotate identified input using Presidio Anonymizer. + + :param text: The text for analysis. + :param st_analyze_results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The list of tokens with the identified entities. + + """ + return _get_tokens(text, st_analyze_results, is_full_html) + + +def _process( + text: str, + model: pa.AnalyzerEngine, + score_threshold: float, + entities: List[str] = None, + entities_operator_map: dict = None, + is_full_text: bool = True, +) -> Tuple[str, list]: + """ + Process the text of str using the model. + + :param text: Text to process + :param model: Model to use for processing + :param entities: Entities to recognize + :param entities_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params. + :param score_threshold: The score threshold to use for recognition + :param is_full_text: Whether to return the full text or just the annotated text + + :returns: A tuple of: + + * the anonymized text + * the list of Presidio RecognizerResult constructed from analysis + """ + + # get the analyzer engine + analyzer = model + + # analyze the text that can be used for anonymization + results = analyzer.analyze( + text=text, + language="en", + entities=entities, + score_threshold=score_threshold, + return_decision_process=True, + ) + + # anonymize the text, replace the pii entities with the labels + anonymized_text = _anonymize(text, results, entities_operator_map, is_full_text) + + return anonymized_text, results + + +def _get_single_html( + text: str, results: List[pa.RecognizerResult], is_full_html: bool = True +): + """ + Generate the html for a single txt file. + + :param text: The text for analysis. + :param results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The html string for a single txt file. + """ + # convert the results to tokens to generate the html + tokens = _annotate(text, results, is_full_html) + html = at_util.get_annotated_html(*tokens) + + # avoid the error during rendering of the \n in the html + backslash_char = "\\" + + html_str = f"

    {html.replace('{backslash_char}n', '
    ')}

    " + + return html_str + + +def _get_single_json(results: List[pa.RecognizerResult], is_full_report: bool = True): + """ + Generate the json for a single txt file. + + :param results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full json or not. + + :returns: The json string for a single txt file. + """ + # generate the stats report if needed + if not is_full_report: + stats = [] + # add the simplify stats logic here + for item in results: + item.analysis_explanation = None + stats.append(item) + else: + stats = results + + return stats + + +def _get_all_html( + txt_content: dict, + res_dict: dict, + is_full_html: bool = True, +): + """ + Generate the html for all txt files. + + :param txt_content: The dictionary of txt file name and content. + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The html string for all txt files. + + """ + # These are placeholder for the html string + html_index = "Highlighted Pii Entities

    Highlighted Pii Entities

      " + html_content = "" + for txt_file, results in res_dict.items(): + txt = txt_content[txt_file] + html_index += f"
    • {txt_file}
    • " + html_content += f"
    • {txt_file}

      {_get_single_html(txt, results, is_full_html)}

    • " + html_index += "
    " + html_res = f"{html_index}{html_content}" + + return html_res + + +def _get_all_rpt(res_dict: dict, is_full_report: bool = True): + """ + Generate the stats report for all txt files. + + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full report or not. + + :returns: The stats report for all txt files. + """ + # These are placeholder for the json report + stats_dict = {} + for txt_file, results in res_dict.items(): + new_stats = [] + for item in _get_single_json(results, is_full_report): + if is_full_report: + item.analysis_explanation = item.analysis_explanation.to_dict() + new_stats.append(item.to_dict()) + else: + tmp_dict = item.to_dict() + tmp_dict.pop("analysis_explanation") + tmp_dict.pop("recognition_metadata") + new_stats.append(tmp_dict) + stats_dict[txt_file] = new_stats + return stats_dict + + +def recognize_pii( + context: mlrun.MLClientCtx, + input_path: Union[str, pathlib.Path], + html_key: str, + score_threshold: float, + output_directory: str = None, + entities: List[ + str + ] = None, # List of entities to recognize, default is recognizing all + entity_operator_map: dict = None, + model: str = None, + generate_json: bool = True, + generate_html: bool = True, + is_full_text: bool = True, + is_full_html: bool = True, + is_full_report: bool = True, +) -> Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, dict]]: + """ + Walk through the input path, recognize PII in text and store the anonymized text in the output path. + Generate the html with different colors for each entity, json report of the explanation. + + :param context: The MLRun context. this is needed for log the artifacts. + :param input_path: The input path of the text files needs to be analyzed. + :param html_key: The html key for the artifact. + :param score_threshold: The score threshold to mark the recognition as trusted. + :param output_directory: The output directory path to store the anonymized text. + :param entities: The list of entities to recognize. + :param entity_operator_map: The map of entity to operator (mask, redact, replace, keep, hash, and its params) + :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole". + :param generate_json: Whether to generate the json report of the explanation. + :param generate_html: Whether to generate the html report of the explanation. + :param is_full_text: Whether to return the full text or only the masked text. + :param is_full_html: Whether to return the full html or just the annotated text + :param is_full_report: Whether to return the full report or just the score and start, end index + + :returns: A tuple of: + + * Path to the output directory + * The json report of the explanation (if generate_json is True) + * A dictionary of errors files that were not processed + + """ + + # Set output directory + if output_directory is None: + output_directory = tempfile.mkdtemp() + + # Create the output directory: + output_directory = pathlib.Path(output_directory) + if not output_directory.exists(): + output_directory.mkdir(parents=True, exist_ok=True) + + txt_files_directory = pathlib.Path(input_path) + successes = [] + errors = {} + + res_dict = {} + txt_content = {} + # Load the model: + analyzer = _get_analyzer_engine(model, entities) + logger.info("Model loaded") + # Go over the text files in the input path, analyze and anonymize them: + for txt_file in tqdm( + list(txt_files_directory.glob("*.txt")), + desc="Processing files", + unit="file", + ): + try: + # Load the str from the text file + text = txt_file.read_text() + txt_content[str(txt_file)] = text + # Process the text to recoginze the pii entities in it + anonymized_text, results = _process( + text=text, + model=analyzer, + entities=entities, + entities_operator_map=entity_operator_map, + score_threshold=score_threshold, + is_full_text=is_full_text, + ) + res_dict[str(txt_file)] = results + # Store the anonymized text in the output path + output_file = output_directory / f"{txt_file.stem}.txt" + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + f.write(anonymized_text) + successes.append([txt_file.name, output_file.name]) + except Exception as e: + errors[str(txt_file)] = str(e) + logger.error(f"Error processing {txt_file}: {e}") + + successes = pd.DataFrame( + successes, + columns=["original_file", "anonymized_file"], + ) + + if generate_html: + # Generate the html report + html_res = _get_all_html(txt_content, res_dict, is_full_html) + # Store the html report in the context + arti_html = mlrun.artifacts.Artifact(body=html_res, format="html", key=html_key) + context.log_artifact(arti_html) + if generate_json: + # Generate the json report + json_res = _get_all_rpt(res_dict, is_full_report) + return str(output_directory), successes, errors, json_res + return str(output_directory), successes, errors diff --git a/functions/master/pii_recognizer/0.4.0/src/requirements.txt b/functions/master/pii_recognizer/0.4.0/src/requirements.txt new file mode 100644 index 00000000..467565d4 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/requirements.txt @@ -0,0 +1,12 @@ +faker +nltk +pandas +streamlit +presidio-anonymizer +presidio-analyzer +torch +st-annotated-text +streamlit +git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653#egg=flair +st-annotated-text +https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl diff --git a/functions/master/pii_recognizer/0.4.0/src/test_pii_recognizer.py b/functions/master/pii_recognizer/0.4.0/src/test_pii_recognizer.py new file mode 100644 index 00000000..81a16611 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/src/test_pii_recognizer.py @@ -0,0 +1,251 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import pytest +import random +from faker import Faker +import mlrun +from pii_recognizer import ( + _process, + _get_analyzer_engine, + _anonymize, + _annotate, + recognize_pii_parallel, +) + + +def generate_routing_number(): + prefix = random.randint(0, 99) + identifier = random.randint(0, 9999999) + identifier_str = str(identifier).zfill(7) + weighted_sum = ( + 3 * (int(str(prefix).zfill(2)[0])) + + 7 * (int(str(prefix).zfill(2)[1])) + + 1 * (int(identifier_str[0])) + + 3 * (int(identifier_str[1])) + + 7 * (int(identifier_str[2])) + + 1 * (int(identifier_str[3])) + + 3 * (int(identifier_str[4])) + + 7 * (int(identifier_str[5])) + + 1 * (int(identifier_str[6])) + ) + check_digit = (10 - (weighted_sum % 10)) % 10 + + routing_number = f"{prefix:02d}{identifier_str}{check_digit}" + + return routing_number + + +def generate_us_itin(): + area_number = random.randint(900, 999) + group_number = random.randint(70, 99) + serial_number = random.randint(0, 9999) + + formatted_itin = f"{area_number:03d}-{group_number:02d}-{serial_number:04d}" + return formatted_itin + + +@pytest.fixture(scope="function") +def fake_data(request): + params = request.param if hasattr(request, "param") else {} + fake = Faker("en_US") + data = { + "name": fake.name(), + "email": fake.email(), + "address": fake.address(), + "phone": fake.phone_number(), + "ssn": fake.ssn(), + "credit_card": fake.credit_card_number(), + "organization": fake.company(), + "location": fake.street_address(), + "date_time": fake.date(), + "mac_address": fake.mac_address(), + "us_bank_number": fake.bban(), + "imei": "".join(str(fake.random_int(0, 9)) for _ in range(14)), + "title": fake.job(), + "license_plate": fake.license_plate(), + "us_passport": fake.passport_number(), + "currency": fake.currency_code(), + "routing_number": generate_routing_number(), + "us_itin": generate_us_itin(), + "age": fake.random_int(1, 100), + "password": fake.password(), + "swift_code": fake.swift(), + } + + data.update(params) + + yield data + + +@pytest.mark.skip() +def test_pattern_process(fake_data): + ENTITIES = { + "CREDIT_CARD": "credit_card", + "SSN": "ssn", + "PHONE": "phone", + "EMAIL": "email", + } + + analyzer = _get_analyzer_engine(model="pattern") + text = f"He can be reached at {fake_data['email']} or {fake_data['phone']}. His credit card number is {fake_data['credit_card']} and his SSN is {fake_data['ssn']}." + res, results = _process(text, analyzer, score_threshold=0.5) + + assert any(entity in res for entity in ENTITIES.keys()) + + +@pytest.mark.skip() +def test_spacy_process(fake_data): + ENTITIES = { + "PERSON": "name", + "ORGANIZATION": "organization", + } + + analyzer = _get_analyzer_engine(model="spacy") + text = f"{fake_data['name']}'s employer is {fake_data['organization']}." + res, results = _process(text, analyzer, score_threshold=0.5) + + assert any(entity in res for entity in ENTITIES.keys()) + + +@pytest.mark.skip() +def test_flair_process(fake_data): + ENTITIES = { + "LOCATION": "location", + "PERSON": "name", + "ORGANIZATION": "organization", + "MAC_ADDRESS": "mac_address", + "US_BANK_NUMBER": "us_bank_number", + "IMEI": "imei", + "TITLE": "title", + "LICENSE_PLATE": "license_plate", + "US_PASSPORT": "us_passport", + "CURRENCY": "currency", + "ROUTING_NUMBER": "routing_number", + "US_ITIN": "us_itin", + "US_BANK_NUMBER": "us_bank_number", + "AGE": "age", + "PASSWORD": "password", + "SWIFT_CODE": "swift_code", + } + + analyzer = _get_analyzer_engine(model="flair") + text = " ".join( + [item + " is " + str(fake_data[item]) for item in ENTITIES.values()] + ) + res, results = _process(text, analyzer, score_threshold=0.5) + assert any(entity in res for entity in ENTITIES.keys()) + + +@pytest.mark.skip() +def test_whole_process(fake_data): + ENTITIES = { + "LOCATION": "location", + "PERSON": "name", + "ORGANIZATION": "organization", + "MAC_ADDRESS": "mac_address", + "US_BANK_NUMBER": "us_bank_number", + "IMEI": "imei", + "TITLE": "title", + "LICENSE_PLATE": "license_plate", + "US_PASSPORT": "us_passport", + "CURRENCY": "currency", + "ROUTING_NUMBER": "routing_number", + "US_ITIN": "us_itin", + "US_BANK_NUMBER": "us_bank_number", + "AGE": "age", + "CREDIT_CARD": "credit_card", + "SSN": "ssn", + "PHONE": "phone", + "EMAIL": "email", + "PASSWORD": "password", + "SWIFT_CODE": "swift_code", + } + text = " ".join( + [item + " is " + str(fake_data[item]) for item in ENTITIES.values()] + ) + analyzer = _get_analyzer_engine(model="whole") + res, results = _process(text, analyzer, score_threshold=0.5) + assert any(entity in res for entity in ENTITIES.keys()) + + +@pytest.mark.skip() +def test_only_entities(fake_data): + ENTITIES = { + "LOCATION": "location", + "PERSON": "name", + "ORGANIZATION": "organization", + "MAC_ADDRESS": "mac_address", + "US_BANK_NUMBER": "us_bank_number", + "IMEI": "imei", + "TITLE": "title", + "LICENSE_PLATE": "license_plate", + "US_PASSPORT": "us_passport", + "CURRENCY": "currency", + "ROUTING_NUMBER": "routing_number", + "US_ITIN": "us_itin", + "US_BANK_NUMBER": "us_bank_number", + "AGE": "age", + "CREDIT_CARD": "credit_card", + "SSN": "ssn", + "PHONE": "phone", + "EMAIL": "email", + "PASSWORD": "password", + "SWIFT_CODE": "swift_code", + } + + text = " ".join( + [item + " is " + str(fake_data[item]) for item in ENTITIES.values()] + ) + analyzer = _get_analyzer_engine(entities=list(ENTITIES.keys())[:5]) + res, results = _process(text, analyzer, score_threshold=0.5) + assert any(entity in res for entity in ENTITIES.keys()) + + +def test_parallel(): + context = mlrun.get_or_create_ctx("test_parallel") + ENTITIES = { + "LOCATION": "location", + "PERSON": "name", + "ORGANIZATION": "organization", + "MAC_ADDRESS": "mac_address", + "US_BANK_NUMBER": "us_bank_number", + "IMEI": "imei", + "TITLE": "title", + "LICENSE_PLATE": "license_plate", + "US_PASSPORT": "us_passport", + "CURRENCY": "currency", + "ROUTING_NUMBER": "routing_number", + "US_ITIN": "us_itin", + "US_BANK_NUMBER": "us_bank_number", + "AGE": "age", + "CREDIT_CARD": "credit_card", + "SSN": "ssn", + "PHONE": "phone", + "EMAIL": "email", + "PASSWORD": "password", + "SWIFT_CODE": "swift_code", + } + json_res, erros = recognize_pii_parallel( + context=context, + config_input_output="data/config.csv", + score_threshold=0.5, + html_key="test_parallel", + entities=list(ENTITIES.keys()), + model="whole", + ) + + assert len(json_res) == 2 diff --git a/functions/master/pii_recognizer/0.4.0/static/documentation.html b/functions/master/pii_recognizer/0.4.0/static/documentation.html new file mode 100644 index 00000000..fbf1f167 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/documentation.html @@ -0,0 +1,552 @@ + + + + + + + +pii_recognizer package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    pii_recognizer package#

    +
    +

    Submodules#

    +
    +
    +

    pii_recognizer.pii_recognizer module#

    +
    +
    +class pii_recognizer.pii_recognizer.CustomSpacyRecognizer(*args: Any, **kwargs: Any)[source]#
    +

    Bases: LocalRecognizer

    +

    Custom Spacy Recognizer from Presidio Analyzer trained on Privy data. +The privy data is generated using this pixie-io/pixie +It can be used to recognize custom entities, Since we want to use Presidio’s Registries to generate AnalyzerEngine, +it inherits from Presidio Analyzer’s LocalRecognizer class.

    +
    +
    +RECOGNIZABLE_ENTITIES = {'DATE_TIME', 'LOCATION', 'NRP', 'ORGANIZATION', 'PERSON'}#
    +
    +
    +
    +analyze(text: str, entities: List[str], nlp_artifacts=None)[source]#
    +

    Analyze text using Spacy.

    +
    +
    Parameters:
    +
      +
    • text – Text to analyze

    • +
    • entities – Entities to analyze

    • +
    • nlp_artifacts – NLP artifacts to use

    • +
    +
    +
    Returns:
    +

    List of Presidio RecognizerResult objects

    +
    +
    +
    +
    +
    +
    +class pii_recognizer.pii_recognizer.Entities[source]#
    +

    Bases: object

    +
    +
    +AGE = 'AGE'#
    +
    +
    +
    +CREDIT_CARD = 'CREDIT_CARD'#
    +
    +
    +
    +CURRENCY = 'CURRENCY'#
    +
    +
    +
    +DATE_TIME = 'DATE_TIME'#
    +
    +
    +
    +EMAIL = 'EMAIL'#
    +
    +
    +
    +GPE = ('GPE',)#
    +
    +
    +
    +IMEI = 'IMEI'#
    +
    +
    +
    +LICENSE_PLATE = 'LICENSE_PLATE'#
    +
    +
    +
    +LOCATION = 'LOCATION'#
    +
    +
    +
    +MAC_ADDRESS = 'MAC_ADDRESS'#
    +
    +
    +
    +NRP = 'NRP'#
    +
    +
    +
    +ORGANIZATION = 'ORGANIZATION'#
    +
    +
    +
    +PASSWORD = 'PASSWORD'#
    +
    +
    +
    +PERSON = 'PERSON'#
    +
    +
    +
    +PHONE = 'PHONE'#
    +
    +
    +
    +ROUTING_NUMBER = 'ROUTING_NUMBER'#
    +
    +
    +
    +SSN = 'SSN'#
    +
    +
    +
    +SWIFT_CODE = 'SWIFT_CODE'#
    +
    +
    +
    +TITLE = 'TITLE'#
    +
    +
    +
    +US_BANK_NUMBER = 'US_BANK_NUMBER'#
    +
    +
    +
    +US_DRIVER_LICENSE = 'US_DRIVER_LICENSE'#
    +
    +
    +
    +US_ITIN = 'US_ITIN'#
    +
    +
    +
    +US_PASSPORT = 'US_PASSPORT'#
    +
    +
    +
    +
    +class pii_recognizer.pii_recognizer.FlairRecognizer(*args: Any, **kwargs: Any)[source]#
    +

    Bases: EntityRecognizer

    +

    Wrapper for a flair model, if needed to be used within Presidio Analyzer. +This is to make sure the recognizer can be registered with Presidio registry.

    +
    +
    +RECOGNIZABLE_ENTITIES = {'AGE', 'CURRENCY', 'GPE', 'IMEI', 'LICENSE_PLATE', 'LOCATION', 'MAC_ADDRESS', 'NRP', 'ORGANIZATION', 'PASSWORD', 'PERSON', 'ROUTING_NUMBER', 'SWIFT_CODE', 'TITLE', 'US_BANK_NUMBER', 'US_DRIVER_LICENSE', 'US_ITIN', 'US_PASSPORT'}#
    +
    +
    +
    +analyze(text: str, entities: List[str], nlp_artifacts: presidio_analyzer.nlp_engine.NlpArtifacts | None = None) List[presidio_analyzer.RecognizerResult][source]#
    +

    Analyze text and return the results.

    +
    +
    Parameters:
    +
      +
    • text – The text for analysis.

    • +
    • entities – The list of entities to recognize.

    • +
    • nlp_artifacts – Not used by this recognizer but needed for the interface.

    • +
    +
    +
    Returns:
    +

    The list of Presidio RecognizerResult constructed from the recognized Flair detections.

    +
    +
    +
    +
    +
    +
    +class pii_recognizer.pii_recognizer.Models[source]#
    +

    Bases: object

    +
    +
    +FLAIR = 'flair'#
    +
    +
    +
    +PATTERN = 'pattern'#
    +
    +
    +
    +SPACY = 'spacy'#
    +
    +
    +
    +WHOLE = 'whole'#
    +
    +
    +
    +
    +class pii_recognizer.pii_recognizer.PatternRecognizerFactory[source]#
    +

    Bases: object

    +

    Factory for creating pattern recognizers, it can be extended in the future to +add more regex pattern for different entities. For the pattern recognizer to work, +we need construct a list of regex patterns for each entity.

    +
    +
    +RECOGNIZABLE_ENTITIES = {'CREDIT_CARD': [presidio_analyzer.Pattern], 'EMAIL': [presidio_analyzer.Pattern], 'PHONE': [presidio_analyzer.Pattern], 'SSN': [presidio_analyzer.Pattern]}#
    +
    +
    +
    +
    +pii_recognizer.pii_recognizer.recognize_pii(context: MLClientCtx, input_path: str | Path, html_key: str, score_threshold: float, output_directory: str | None = None, entities: List[str] | None = None, entity_operator_map: dict | None = None, model: str | None = None, generate_json: bool = True, generate_html: bool = True, is_full_text: bool = True, is_full_html: bool = True, is_full_report: bool = True) Tuple[str, DataFrame, dict, dict] | Tuple[str, DataFrame, dict][source]#
    +

    Walk through the input path, recognize PII in text and store the anonymized text in the output path. +Generate the html with different colors for each entity, json report of the explanation.

    +
    +
    Parameters:
    +
      +
    • context – The MLRun context. this is needed for log the artifacts.

    • +
    • input_path – The input path of the text files needs to be analyzed.

    • +
    • html_key – The html key for the artifact.

    • +
    • score_threshold – The score threshold to mark the recognition as trusted.

    • +
    • output_directory – The output directory path to store the anonymized text.

    • +
    • entities – The list of entities to recognize.

    • +
    • entity_operator_map – The map of entity to operator (mask, redact, replace, keep, hash, and its params)

    • +
    • model – The model to use. Can be “spacy”, “flair”, “pattern” or “whole”.

    • +
    • generate_json – Whether to generate the json report of the explanation.

    • +
    • generate_html – Whether to generate the html report of the explanation.

    • +
    • is_full_text – Whether to return the full text or only the masked text.

    • +
    • is_full_html – Whether to return the full html or just the annotated text

    • +
    • is_full_report – Whether to return the full report or just the score and start, end index

    • +
    +
    +
    Returns:
    +

    A tuple of:

    +
      +
    • Path to the output directory

    • +
    • The json report of the explanation (if generate_json is True)

    • +
    • A dictionary of errors files that were not processed

    • +
    +

    +
    +
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/static/example.html b/functions/master/pii_recognizer/0.4.0/static/example.html new file mode 100644 index 00000000..e403a056 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/example.html @@ -0,0 +1,1880 @@ + + + + + + + +PII Recognizer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    PII Recognizer#

    +

    A function to detect pii data and anonymize the pii entity in the text.

    +

    In this notebook we will go over the function’s docs and outputs and see an end-to-end example of running it.

    +
      +
    1. Documentation

    2. +
    3. Results

    4. +
    5. End-to-end Demo

    6. +
    +

    +
    +

    1. Documentation#

    +

    The function receive a directory path with all the text files in it. It walk through the directory, get all the text file. Then it detect the pii entity inside of the text file, apply the operator on the entity. Generate the html file with all pii entity highlighted. Generate the json report has the explaination of the process.

    +
    +

    1.1. Parameters:#

    +
      +
    • context: mlrun.MLClientCtx

      +

      The MLRun context

      +
    • +
    • input_path: str

      +

      The input directory with all the text files

      +
    • +
    • output_path: str

      +

      The directory that is used to store the anonymized text files. it is also used for mlrun to log the artifact as zip file

      +
    • +
    • output_suffix: str

      +

      The suffix will added to the input file. for example if the input text file is pii.txt, if output_suffix is “anonymized”, the output file would be pii_anonymized.txt

      +
    • +
    • html_key: str

      +

      The artifact name of the html file

      +
    • +
    • entities: List[str]

      +

      The list of the entities to recognize. Please make sure the model you choose can recognize the entities.

      +
    • +
    • entity_operator_map: List[str] +For different entity, we can apply different operator. Now supports Keep, Mask, Replace, Redact, Hash

      +
      +   entity_operator_map = {
      +      "PERSON": ("keep", {}),
      +      "EMAIL": ("mask", {"masking_char": "#", "chars_to_mask": 5, "from_end": False}),
      +      "PHONE": ("hash", {}),
      +      "LOCATION": ("redact", {}),
      +      "ORGANIZATION": ("replace", {"new_value": "Company XYZ"})
      +      }
      +   
      +

      In this example:

      +
        +
      • “PERSON” entities are kept as they are using the “keep” operator.

      • +
      • “EMAIL_ADDRESS” entities are masked with the “#” character, masking the first five characters.

      • +
      • “PHONE_NUMBER” entities are replaced with their hashed value using the “hash” operator.

      • +
      • “LOCATION” entities are completely removed using the “redact” operator.

      • +
      • “ORGANIZATION” entities are replaced with the string “Company XYZ” using the “replace” operator.

      • +
      +
    • +
    • model: str

      +
        +
      • “whole”, “spacy”, “pattern”, “flair”. The default is “whole”.

      • +
      +

      For each model, it can detect some entities. The “whole” model is combined all three models together. It can detect all the entities list below.

      +
        +
      • “spacy” : [“LOCATION”, “PERSON”,”NRP”,”ORGANIZATION”,”DATE_TIME”]

      • +
      • “pattern”: [“CREDIT_CARD”, “SSN”, “PHONE”, “EMAIL”]

      • +
      • “flair”: [ “LOCATION”, +“PERSON”, +“NRP”, +“GPE”, +“ORGANIZATION”, +“MAC_ADDRESS”, +“US_BANK_NUMBER”, +“IMEI”, +“TITLE”, +“LICENSE_PLATE”, +“US_PASSPORT”, +“CURRENCY”, +“ROUTING_NUMBER”, +“US_ITIN”, +“US_BANK_NUMBER”, +“US_DRIVER_LICENSE”, +“AGE”, +“PASSWORD”, +“SWIFT_CODE” +]

      • +
      +
    • +
    • score_threshold:

      +

      Minimum confidence value, the default is 0 to align with presidio.AnalyzerEngine

      +
    • +
    • generate_json_rpt:

      +

      Whether to generate the json report of the explaination

      +
    • +
    • generate_html_rpt:

      +

      Whether to generate the html with highlighted pii entities or not

      +
    • +
    • is_full_text:

      +

      Whether to return the full text or just the sentences with pii entities.

      +
    • +
    • is_full_html: bool

      +

      Whether to return the full html or just the annotated html

      +
    • +
    • is_full_report: bool

      +

      Whether to return the full json report or just the score and start, end index

      +
    • +
    +
    +
    +

    1.2. Outputs:#

    +

    There are two outputs of this function.

    +
      +
    • output_path: str

      +

      The directory stored all the anonymized text files

      +
    • +
    • rpt_json: dict

      +

      A dict of reporting to explain how does the model detect the pii entity

      +
    • +
    • errors : dict +A dict of errors when processing the text files if any

    • +
    +

    +
    +
    +
    +

    2. Results#

    +

    The result of the function looks like the following:

    +

    For example if the input string is

    +

    John Doe 's ssn is 182838483, connect john doe with john_doe@gmail.com or 6288389029, he can pay you with 41482929939393

    +

    The anonymized_text is

    +

    <PERSON>'s <ORGANIZATION> is <SSN>, connect <PERSON> with <PERSON> <EMAIL> or <PHONE>, he can pay you with <CREDIT_CARD>

    +

    The html_str is

    +

    John Doe'sPERSON ssnORGANIZATION is 182838483SSN, connect me with john_doe@gmail.comPERSONjohn_doe@gmail.comEMAIL or 6288389029PHONE, he can pay you with 41482929939393CREDIT_CARD +

    +

    The json report that explain the output is

    +
    [
    +  {
    +    "entity_type": "PERSON", # result of the labeling
    +    "start": 0, # start positon of the entity
    +    "end": 9,  # end postion of the entity
    +    "score": 0.99, # the confident score of the model + context_improvement
    +    "analysis_explanation": {
    +      "recognizer": "FlairRecognizer", # which recognizer is used to recognize this entity
    +      "pattern_name": null,
    +      "pattern": null,
    +      "original_score": 0.99, # The original confident score from the pre-trained model
    +      "score": 0.99, # the final score = original_score + score_context_improvement
    +      "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +      "score_context_improvement": 0, # The improvement from the context
    +      "supportive_context_word": "",
    +      "validation_result": null
    +    },
    +    "recognition_metadata": {
    +      "recognizer_identifier": "Flair Analytics_5577088640",
    +      "recognizer_name": "Flair Analytics"
    +    }
    +  },
    +  ....
    +]
    +
    +
    +

    +
    +
    +

    3. End-to-end Demo#

    +
    +

    3.1. Recognition configurations#

    +
      +
    • model: which model you want to use.

    • +
    • entities: What entities to recognize?

    • +
    • score_threshold: From which score to mark the recogniztion as trusted?

    • +
    +
    +
    +
    import mlrun
    +artifact_path = "./"
    +proj = mlrun.get_or_create_project("pii", "./")
    +fn = mlrun.code_to_function(
    +    project="pii",
    +    name="pii_recognizer",
    +    filename="pii_recognizer.py",
    +    kind="job",
    +    image="mlrun/mlrun",
    +    handler="recognize_pii",
    +    description="This function is used to recognize PII in a given text",
    +)
    +run_obj = fn.run(
    +    artifact_path = artifact_path,
    +    params= {
    +        'model': "whole", 
    +        'input_path': "./data/",
    +        'output_path': "./data/output1/",
    +        "entities": ['PERSON', "EMAIL", "PHONE", "LOCATION", "ORGANIZATION"], # the entities that needs to recognize
    +        "output_suffix": "output",
    +        "html_key": "highlighted",
    +        "score_threshold" : 0.5, # the score threshold to mark the recognition as trusted
    +    },
    +    returns = ["output_path: path", "rpt_json: file", "errors: file"],
    +    local=True,
    +)
    +
    +
    +
    +
    +
    > 2023-07-31 02:17:04,305 [info] Project loaded successfully: {'project_name': 'pii'}
    +> 2023-07-31 02:17:04,312 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}
    +> 2023-07-31 02:17:04,408 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}
    +> 2023-07-31 02:17:04,409 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '51b5ad8144004e52a1008c08850842c8', 'db': None}
    +2023-07-31 02:17:04,567 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin
    +2023-07-31 02:17:07,730 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP
    +Model loaded
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    pii0Jul 31 02:17:04completedpii-recognizer-recognize-pii
    v3io_user=pengw
    kind=
    owner=pengw
    host=jupyter-pengw-5f99fb678d-mnvxl
    model=whole
    input_path=./data/
    output_path=./data/output1/
    entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
    output_suffix=output
    html_key=highlighted
    score_threshold=0.5
    highlighted
    output_path
    rpt_json
    errors
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods or click here to open in UI
    > 2023-07-31 02:17:12,403 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}
    +
    +
    +
    +
    +
    +
    +
    #get the mlrun context
    +context = mlrun.get_or_create_ctx('pii_ctx1')
    +import pathlib
    +from tqdm.auto import tqdm
    +for i, txt_file in enumerate(
    +        tqdm(
    +            list(pathlib.Path("./data/output1/").glob("*.txt")),
    +            desc="Processing files",
    +            unit="file",
    +        )
    +    ):
    +            # Load the str from the text file
    +        text = txt_file.read_text()
    +        print(text)
    +
    +
    +
    +
    +
    Dear Mr. <PERSON>,
    +
    +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of <ORGANIZATION>. Your flight tickets have been booked, and you will be departing on July 15th, 2023.
    +
    +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations.
    +
    +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team.
    +
    +<PERSON> <ORGANIZATION> is 182838483, connect him with <EMAIL> or <PHONE>, he can pay you with <PHONE>9393
    +
    +
    +
    +
    +
    +
    +
    #check the highlighted html 
    +html_output = context.get_cached_artifact("highlighted")
    +html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode("utf-8")
    +from IPython.core.display import display, HTML
    +display(HTML(html_str))
    +
    +
    +
    +
    +
    Highlighted Pii Entities

    Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON, + +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION. Your flight tickets have been booked, and you will be departing on July 15th, 2023. + +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations. + +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team. +

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON@gmail.com or 6288389029PHONE, he can pay you with 4148292993PHONE9393

  • +
    +
    +
    +
    #check the json report about the explanation.
    +rpt_output1 = context.get_cached_artifact("rpt_json")
    +rpt_str1 = mlrun.get_dataitem(rpt_output1.get_target_path()).get().decode("utf-8")
    +import json
    +obj = json.loads(rpt_str1)
    + 
    +# Pretty Print JSON
    +json_formatted_str1 = json.dumps(obj, indent=4)
    +print(json_formatted_str1)
    +
    +
    +
    +
    +
    {
    +    "data/letter.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 9,
    +            "end": 17,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139944219101744"
    +            }
    +        },
    +        {
    +            "entity_type": "LOCATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1.0,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1.0,
    +                "score": 1.0,
    +                "textual_explanation": "Identified as LOC by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944219101936",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139944219101744"
    +            }
    +        }
    +    ],
    +    "data/pii_data.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 0,
    +            "end": 12,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139944219101744"
    +            }
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 13,
    +            "end": 16,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139944219101744"
    +            }
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 53,
    +            "end": 58,
    +            "score": 1.0,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1.0,
    +                "score": 1.0,
    +                "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944219101936",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 48,
    +            "end": 52,
    +            "score": 0.87,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 0.87,
    +                "score": 0.87,
    +                "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944219101936",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "EMAIL",
    +            "start": 48,
    +            "end": 68,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "EMAIL",
    +                "pattern": "\\S+@\\S+",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139944352474640"
    +            }
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 72,
    +            "end": 82,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "PHONE",
    +                "pattern": "\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139944352476560"
    +            }
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 104,
    +            "end": 114,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "PHONE",
    +                "pattern": "\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139944352476560"
    +            }
    +        }
    +    ]
    +}
    +
    +
    +
    +
    +
    +
    +

    3.2. Masking configurations#

    +
      +
    • entity_operator_map: it defined what to do with recognized tokens? Mask them? mask them with what? remove them? replace them?

    • +
    +
    +     entity_operator_map = {
    +        "PERSON": ("keep", {}),
    +        "EMAIL": ("mask", {"masking_char": "😀", "chars_to_mask": 5, "from_end": False}),
    +        "PHONE": ("hash", {}),
    +        "LOCATION": ("redact", {}),
    +        "ORGANIZATION": ("replace", {"new_value": "Company XYZ"})
    +        }
    +     
    +
    +
    import mlrun
    +artifact_path = "./"
    +proj = mlrun.get_or_create_project("pii", "./")
    +fn = mlrun.code_to_function(
    +    project="pii",
    +    name="pii_recognizer",
    +    filename="pii_recognizer.py",
    +    kind="job",
    +    image="mlrun/mlrun",
    +    handler="recognize_pii",
    +    description="This function is used to recognize PII in a given text",
    +)
    +
    +entity_operator_map = {
    +        "PERSON": ("keep", {}),
    +        "EMAIL": ("mask", {"masking_char": "😀", "chars_to_mask" : 100, "from_end": False}),
    +        "PHONE": ("hash", {}),
    +        "LOCATION": ("redact", {}),
    +        "ORGANIZATION": ("replace", {"new_value": "Company XYZ"})
    +        }
    +run_obj = fn.run(
    +    artifact_path = artifact_path,
    +    params= {
    +        'model': "whole", 
    +        'input_path': "./data/",
    +        'output_path': "./data/output2/",
    +        "entities": ['PERSON', "EMAIL", "PHONE", "LOCATION", "ORGANIZATION"],
    +        "output_suffix": "output",
    +        "html_key": "highlighted",
    +        "score_threshold" : 0.5,
    +        "entity_operator_map": entity_operator_map,
    +        
    +    },
    +    returns = ["output_path: path", "rpt_json: file", "errors: file"],
    +    local=True,
    +)
    +
    +
    +
    +
    +
    > 2023-07-31 02:20:40,550 [info] Project loaded successfully: {'project_name': 'pii'}
    +> 2023-07-31 02:20:40,556 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}
    +> 2023-07-31 02:20:40,649 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}
    +> 2023-07-31 02:20:40,649 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '2b43f80c7ca44b43b229760bb55f814d', 'db': None}
    +2023-07-31 02:20:40,812 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin
    +2023-07-31 02:20:44,130 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP
    +Model loaded
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    pii0Jul 31 02:20:40completedpii-recognizer-recognize-pii
    v3io_user=pengw
    kind=
    owner=pengw
    host=jupyter-pengw-5f99fb678d-mnvxl
    model=whole
    input_path=./data/
    output_path=./data/output2/
    entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
    output_suffix=output
    html_key=highlighted
    score_threshold=0.5
    entity_operator_map={'PERSON': ('keep', {}), 'EMAIL': ('mask', {'masking_char': '😀', 'chars_to_mask': 100, 'from_end': False, 'entity_type': 'EMAIL'}), 'PHONE': ('hash', {}), 'LOCATION': ('redact', {}), 'ORGANIZATION': ('replace', {'new_value': 'Company XYZ', 'entity_type': 'ORGANIZATION'})}
    highlighted
    output_path
    rpt_json
    errors
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods or click here to open in UI
    > 2023-07-31 02:20:48,903 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}
    +
    +
    +
    +
    +
    +
    +
    #get the mlrun context
    +context = mlrun.get_or_create_ctx('pii_ctx1')
    +import pathlib
    +from tqdm.auto import tqdm
    +for i, txt_file in enumerate(
    +        tqdm(
    +            list(pathlib.Path("./data/output2/").glob("*.txt")),
    +            desc="Processing files",
    +            unit="file",
    +        )
    +    ):
    +            # Load the str from the text file
    +        text = txt_file.read_text()
    +        print(text)
    +
    +
    +
    +
    +
    Dear Mr. John Doe,
    +
    +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of Company XYZ. Your flight tickets have been booked, and you will be departing on July 15th, 2023.
    +
    +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations.
    +
    +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team.
    +
    +John smith's Company XYZ is 182838483, connect him with 😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀 or 3990096a212e92850c3b3c8e57ab398252d482444a32def6b030cbac2d51efa3, he can pay you with a6983d9477e93eab115305afd124bd096699e6cb7d2ce72ec6e29a6378a4e8059393
    +
    +
    +
    +
    +
    +
    +
    #check the highlighted html 
    +html_output = context.get_cached_artifact("highlighted")
    +html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode("utf-8")
    +from IPython.core.display import display, HTML
    +display(HTML(html_str))
    +
    +
    +
    +
    +
    Highlighted Pii Entities

    Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON, + +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION. Your flight tickets have been booked, and you will be departing on July 15th, 2023. + +Please provide us with the necessary details to finalize your travel arrangements. We kindly request your full name, date of birth, passport number, and contact information. Rest assured that all provided information will be handled with utmost confidentiality and in compliance with data protection regulations. + +We look forward to creating unforgettable memories for you and your loved ones during your stay with us. If you have any questions or require further assistance, please don't hesitate to contact our customer support team. +

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON@gmail.com or 6288389029PHONE, he can pay you with 4148292993PHONE9393

  • +
    +
    +
    +
    #check the json report about the explanation.
    +rpt_output1 = context.get_cached_artifact("rpt_json")
    +rpt_str1 = mlrun.get_dataitem(rpt_output1.get_target_path()).get().decode("utf-8")
    +import json
    +obj = json.loads(rpt_str1)
    + 
    +# Pretty Print JSON
    +json_formatted_str1 = json.dumps(obj, indent=4)
    +print(json_formatted_str1)
    +
    +
    +
    +
    +
    {
    +    "data/letter.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 9,
    +            "end": 17,
    +            "score": 1.0,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1.0,
    +                "score": 1.0,
    +                "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944345555488",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "LOCATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1.0,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1.0,
    +                "score": 1.0,
    +                "textual_explanation": "Identified as LOC by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944345555488",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139943499301312"
    +            }
    +        }
    +    ],
    +    "data/pii_data.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 0,
    +            "end": 12,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as PERSON by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139943499301312"
    +            }
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 13,
    +            "end": 16,
    +            "score": 1,
    +            "analysis_explanation": {
    +                "recognizer": "CustomSpacyRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1,
    +                "score": 1,
    +                "textual_explanation": "Identified as ORG by Spacy's Named Entity Recognition (Privy-trained)",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "CustomSpacyRecognizer",
    +                "recognizer_identifier": "CustomSpacyRecognizer_139943499301312"
    +            }
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 53,
    +            "end": 58,
    +            "score": 1.0,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 1.0,
    +                "score": 1.0,
    +                "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944345555488",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 48,
    +            "end": 52,
    +            "score": 0.87,
    +            "analysis_explanation": {
    +                "recognizer": "FlairRecognizer",
    +                "pattern_name": null,
    +                "pattern": null,
    +                "original_score": 0.87,
    +                "score": 0.87,
    +                "textual_explanation": "Identified as PER by Flair's Named Entity Recognition",
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_identifier": "Flair Analytics_139944345555488",
    +                "recognizer_name": "Flair Analytics"
    +            }
    +        },
    +        {
    +            "entity_type": "EMAIL",
    +            "start": 48,
    +            "end": 68,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "EMAIL",
    +                "pattern": "\\S+@\\S+",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139943864893792"
    +            }
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 72,
    +            "end": 82,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "PHONE",
    +                "pattern": "\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139943864894128"
    +            }
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 104,
    +            "end": 114,
    +            "score": 0.5,
    +            "analysis_explanation": {
    +                "recognizer": "PatternRecognizer",
    +                "pattern_name": "PHONE",
    +                "pattern": "\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}",
    +                "original_score": 0.5,
    +                "score": 0.5,
    +                "textual_explanation": null,
    +                "score_context_improvement": 0,
    +                "supportive_context_word": "",
    +                "validation_result": null
    +            },
    +            "recognition_metadata": {
    +                "recognizer_name": "PatternRecognizer",
    +                "recognizer_identifier": "PatternRecognizer_139943864894128"
    +            }
    +        }
    +    ]
    +}
    +
    +
    +
    +
    +
    +
    +

    3.3 Output configurations#

    +
      +
    • is_full_text: whether produce full text or just the sentences have PII entities in it

    • +
    • generate_html: whether to produce the html with highlighted pii entities

    • +
    • generate_json: whether to proudce the json report with the explaination of the process

    • +
    • is_full_html: whether produce full text with the pii entities highlighted or just sentences with pii entities.

    • +
    • is_full_report: whether produce the json report with detailed information or just start, end index and scores.

    • +
    +
    +
    +
    import mlrun
    +artifact_path = "./"
    +proj = mlrun.get_or_create_project("pii", "./")
    +fn = mlrun.code_to_function(
    +    project="pii",
    +    name="pii_recognizer",
    +    filename="pii_recognizer.py",
    +    kind="job",
    +    image="mlrun/mlrun",
    +    handler="recognize_pii",
    +    description="This function is used to recognize PII in a given text",
    +)
    +
    +entity_operator_map = {
    +        "PERSON": ("keep", {}),
    +        "EMAIL": ("mask", {"masking_char": "😀", "chars_to_mask" : 100, "from_end": False}),
    +        "PHONE": ("hash", {}),
    +        "LOCATION": ("redact", {}),
    +        "ORGANIZATION": ("replace", {"new_value": "Company XYZ"})
    +        }
    +run_obj = fn.run(
    +    artifact_path = artifact_path,
    +    params= {
    +        'model': "whole", 
    +        'input_path': "./data/",
    +        'output_path': "./data/output3/",
    +        "entities": ['PERSON', "EMAIL", "PHONE", "LOCATION", "ORGANIZATION"],
    +        "output_suffix": "output",
    +        "html_key": "highlighted",
    +        "score_threshold" : 0.5,
    +        "entity_operator_map": entity_operator_map,
    +        "is_full_text": False,
    +        "is_full_html": False,
    +        "is_full_report": False,
    +    },
    +    returns = ["output_path: path", "rpt_json: file", "errors: file"],
    +    local=True,
    +)
    +
    +
    +
    +
    +
    > 2023-07-31 02:22:57,789 [info] Project loaded successfully: {'project_name': 'pii'}
    +> 2023-07-31 02:22:57,799 [warning] Failed to add git metadata, ignore if path is not part of a git repo.: {'path': './', 'error': '/User/pii_recognizer'}
    +> 2023-07-31 02:22:57,891 [warning] artifact/output path is not defined or is local and relative, artifacts will not be visible in the UI: {'output_path': './'}
    +> 2023-07-31 02:22:57,892 [info] Storing function: {'name': 'pii-recognizer-recognize-pii', 'uid': '3f6d701e423346b39026dc365698c15c', 'db': None}
    +2023-07-31 02:22:58,079 loading file /User/.flair/models/flair-pii-distilbert/models--beki--flair-pii-distilbert/snapshots/20fb59f1762edcf253bce67716a94a43cb075ae6/pytorch_model.bin
    +2023-07-31 02:23:01,565 SequenceTagger predicts: Dictionary with 21 tags: O, S-LOC, B-LOC, E-LOC, I-LOC, S-PER, B-PER, E-PER, I-PER, S-DATE_TIME, B-DATE_TIME, E-DATE_TIME, I-DATE_TIME, S-ORG, B-ORG, E-ORG, I-ORG, S-NRP, B-NRP, E-NRP, I-NRP
    +Model loaded
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    pii0Jul 31 02:22:57completedpii-recognizer-recognize-pii
    v3io_user=pengw
    kind=
    owner=pengw
    host=jupyter-pengw-5f99fb678d-mnvxl
    model=whole
    input_path=./data/
    output_path=./data/output3/
    entities=['PERSON', 'EMAIL', 'PHONE', 'LOCATION', 'ORGANIZATION']
    output_suffix=output
    html_key=highlighted
    score_threshold=0.5
    entity_operator_map={'PERSON': ('keep', {}), 'EMAIL': ('mask', {'masking_char': '😀', 'chars_to_mask': 100, 'from_end': False, 'entity_type': 'EMAIL'}), 'PHONE': ('hash', {}), 'LOCATION': ('redact', {}), 'ORGANIZATION': ('replace', {'new_value': 'Company XYZ', 'entity_type': 'ORGANIZATION'})}
    is_full_text=False
    is_full_html=False
    is_full_report=False
    highlighted
    output_path
    rpt_json
    errors
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods or click here to open in UI
    > 2023-07-31 02:23:06,096 [info] Run execution finished: {'status': 'completed', 'name': 'pii-recognizer-recognize-pii'}
    +
    +
    +
    +
    +
    +
    +
    #get the mlrun context
    +context = mlrun.get_or_create_ctx('pii_ctx')
    +import pathlib
    +from tqdm.auto import tqdm
    +
    +
    +
    +
    +
    +
    +
    for i, txt_file in enumerate(
    +        tqdm(
    +            list(pathlib.Path("./data/output3/").glob("*.txt")),
    +            desc="Processing files",
    +            unit="file",
    +        )
    +    ):
    +            # Load the str from the text file
    +        text = txt_file.read_text()
    +        print(text)
    +
    +
    +
    +
    +
    Dear Mr. John Doe,
    +
    +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway!
    +John smith's Company XYZ is 182838483, connect him with 😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀 or 3990096a212e92850c3b3c8e57ab398252d482444a32def6b030cbac2d51efa3, he can pay you with a6983d9477e93eab115305afd124bd096699e6cb7d2ce72ec6e29a6378a4e8059393
    +
    +
    +
    +
    +
    +
    +
    #check the highlighted html 
    +html_output = context.get_cached_artifact("highlighted")
    +html_str = mlrun.get_dataitem(html_output.get_target_path()).get().decode("utf-8")
    +from IPython.core.display import display, HTML
    +display(HTML(html_str))
    +
    +
    +
    +
    +
    Highlighted Pii Entities

    Highlighted Pii Entities

  • data/letter.txt

    Dear Mr. John DoePERSON, + +We are pleased to inform you that you have been selected as the winner of our exclusive vacation package giveaway! Congratulations! You, along with your family, will enjoy a luxurious stay at our resort in the beautiful city of RivieraLOCATIONRivieraORGANIZATION

  • data/pii_data.txt

    John smith'sPERSON ssnORGANIZATION is 182838483, connect him with JohnPERSONJohn_smith@gmail.comEMAILsmithPERSON

  • +
    +
    +
    +
    #check the json report about the explanation.
    +rpt_output = context.get_cached_artifact("rpt_json")
    +rpt_str = mlrun.get_dataitem(rpt_output.get_target_path()).get().decode("utf-8")
    +import json
    +obj = json.loads(rpt_str)
    + 
    +# Pretty Print JSON
    +json_formatted_str = json.dumps(obj, indent=4)
    +print(json_formatted_str)
    +
    +
    +
    +
    +
    {
    +    "data/letter.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 9,
    +            "end": 17,
    +            "score": 1
    +        },
    +        {
    +            "entity_type": "LOCATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1.0
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 248,
    +            "end": 255,
    +            "score": 1
    +        }
    +    ],
    +    "data/pii_data.txt": [
    +        {
    +            "entity_type": "PERSON",
    +            "start": 0,
    +            "end": 12,
    +            "score": 1
    +        },
    +        {
    +            "entity_type": "ORGANIZATION",
    +            "start": 13,
    +            "end": 16,
    +            "score": 1
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 53,
    +            "end": 58,
    +            "score": 1.0
    +        },
    +        {
    +            "entity_type": "PERSON",
    +            "start": 48,
    +            "end": 52,
    +            "score": 0.87
    +        },
    +        {
    +            "entity_type": "EMAIL",
    +            "start": 48,
    +            "end": 68,
    +            "score": 0.5
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 72,
    +            "end": 82,
    +            "score": 0.5
    +        },
    +        {
    +            "entity_type": "PHONE",
    +            "start": 104,
    +            "end": 114,
    +            "score": 0.5
    +        }
    +    ]
    +}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/static/function.html b/functions/master/pii_recognizer/0.4.0/static/function.html new file mode 100644 index 00000000..92eba7ca --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/function.html @@ -0,0 +1,150 @@ + + + + + + + + + + + Source + + + + +
    +        
    +verbose: false
    +spec:
    +  default_handler: recognize_pii
    +  entry_points:
    +    analyze:
    +      name: analyze
    +      outputs:
    +      - doc: The list of Presidio RecognizerResult constructed from the recognized
    +          Flair detections.
    +        type: List[pa.RecognizerResult]
    +      has_kwargs: false
    +      parameters:
    +      - name: self
    +      - name: text
    +        type: str
    +        doc: The text for analysis.
    +      - name: entities
    +        type: List[str]
    +        doc: The list of entities to recognize.
    +      - name: nlp_artifacts
    +        type: pa.nlp_engine.NlpArtifacts
    +        doc: Not used by this recognizer but needed for the interface.
    +        default: null
    +      lineno: 381
    +      doc: Analyze text and return the results.
    +      has_varargs: false
    +    recognize_pii:
    +      name: recognize_pii
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame,
    +          dict]]
    +      has_kwargs: false
    +      parameters:
    +      - name: context
    +        type: MLClientCtx
    +        doc: The MLRun context. this is needed for log the artifacts.
    +      - name: input_path
    +        type: Union[str, Path]
    +        doc: The input path of the text files needs to be analyzed.
    +      - name: html_key
    +        type: str
    +        doc: The html key for the artifact.
    +      - name: score_threshold
    +        type: float
    +        doc: The score threshold to mark the recognition as trusted.
    +      - name: output_directory
    +        type: str
    +        doc: The output directory path to store the anonymized text.
    +        default: null
    +      - name: entities
    +        type: List[str]
    +        doc: The list of entities to recognize.
    +        default: null
    +      - name: entity_operator_map
    +        type: dict
    +        doc: The map of entity to operator (mask, redact, replace, keep, hash, and
    +          its params)
    +        default: null
    +      - name: model
    +        type: str
    +        doc: The model to use. Can be "spacy", "flair", "pattern" or "whole".
    +        default: null
    +      - name: generate_json
    +        type: bool
    +        doc: Whether to generate the json report of the explanation.
    +        default: true
    +      - name: generate_html
    +        type: bool
    +        doc: Whether to generate the html report of the explanation.
    +        default: true
    +      - name: is_full_text
    +        type: bool
    +        doc: Whether to return the full text or only the masked text.
    +        default: true
    +      - name: is_full_html
    +        type: bool
    +        doc: Whether to return the full html or just the annotated text
    +        default: true
    +      - name: is_full_report
    +        type: bool
    +        doc: Whether to return the full report or just the score and start, end index
    +        default: true
    +      lineno: 845
    +      doc: 'Walk through the input path, recognize PII in text and store the anonymized
    +        text in the output path.
    +
    +        Generate the html with different colors for each entity, json report of the
    +        explanation.'
    +      has_varargs: false
    +  build:
    +    base_image: mlrun/mlrun
    +    requirements:
    +    - nltk
    +    - pandas
    +    - presidio-anonymizer
    +    - presidio-analyzer
    +    - torch
    +    - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653
    +    - st-annotated-text
    +    - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo=
    +    code_origin: ''
    +    origin_filename: ''
    +  description: This function is used to recognize PII in a directory of text files
    +  image: ''
    +  command: ''
    +  disable_auto_mount: false
    +kind: job
    +metadata:
    +  name: pii-recognizer
    +  tag: ''
    +  categories:
    +  - data-preparation
    +  - NLP
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/static/item.html b/functions/master/pii_recognizer/0.4.0/static/item.html new file mode 100644 index 00000000..d16c5239 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/item.html @@ -0,0 +1,69 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories: 
    +  - data-preparation
    +  - NLP
    +description: This function is used to recognize PII in a directory of text files
    +doc: ''
    +example: pii_recognizer.ipynb
    +generationDate: 2023-08-15:10-24
    +hidden: false
    +icon: ''
    +labels:
    +  author: pgw
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: pii-recognizer
    +platformVersion: 3.5.3
    +spec:
    +  filename: pii_recognizer.py
    +  handler: recognize_pii
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +   - nltk
    +   - pandas
    +   - presidio-anonymizer
    +   - presidio-analyzer
    +   - torch
    +   - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653
    +   - st-annotated-text
    +   - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl
    +url: ''
    +version: 0.4.0
    +test_valid: False
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/static/pii_recognizer.html b/functions/master/pii_recognizer/0.4.0/static/pii_recognizer.html new file mode 100644 index 00000000..d271919b --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/pii_recognizer.html @@ -0,0 +1,1147 @@ + + + + + + + +pii_recognizer.pii_recognizer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for pii_recognizer.pii_recognizer

    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import logging
    +import os
    +import pathlib
    +import tempfile
    +import warnings
    +from typing import List, Set, Tuple, Union
    +
    +import annotated_text.util as at_util
    +import mlrun
    +import nltk
    +import pandas as pd
    +import presidio_analyzer as pa
    +import presidio_anonymizer as pre_anoymizer
    +from presidio_anonymizer.entities import OperatorConfig
    +from tqdm import tqdm
    +
    +try:
    +    import flair as fl
    +except ModuleNotFoundError:
    +    print("Flair is not installed")
    +
    +# There is a conflict between Rust-based tokenizers' parallel processing
    +# and Python's fork operations during multiprocessing. To avoid this, we need
    +# the following two lines
    +
    +os.environ["TOKENIZERS_PARALLELISM"] = "false"
    +warnings.filterwarnings("ignore")
    +
    +logger = logging.getLogger("pii-recognizer")
    +
    +
    +# Add the constant classes of Models and Entities to govern the whole package
    +
    +[docs] +class Models: + WHOLE = "whole" + PATTERN = "pattern" + SPACY = "spacy" + FLAIR = "flair"
    + + + +
    +[docs] +class Entities: + CREDIT_CARD = "CREDIT_CARD" + SSN = "SSN" + PHONE = "PHONE" + EMAIL = "EMAIL" + LOCATION = "LOCATION" + PERSON = "PERSON" + NRP = "NRP" + ORGANIZATION = "ORGANIZATION" + DATE_TIME = "DATE_TIME" + GPE = ("GPE",) + MAC_ADDRESS = "MAC_ADDRESS" + US_BANK_NUMBER = "US_BANK_NUMBER" + IMEI = "IMEI" + TITLE = "TITLE" + LICENSE_PLATE = "LICENSE_PLATE" + US_PASSPORT = "US_PASSPORT" + CURRENCY = "CURRENCY" + ROUTING_NUMBER = "ROUTING_NUMBER" + US_ITIN = "US_ITIN" + US_BANK_NUMBER = "US_BANK_NUMBER" + US_DRIVER_LICENSE = "US_DRIVER_LICENSE" + AGE = "AGE" + PASSWORD = "PASSWORD" + SWIFT_CODE = "SWIFT_CODE"
    + + + +
    +[docs] +class PatternRecognizerFactory: + """ + Factory for creating pattern recognizers, it can be extended in the future to + add more regex pattern for different entities. For the pattern recognizer to work, + we need construct a list of regex patterns for each entity. + """ + + RECOGNIZABLE_ENTITIES = { + "CREDIT_CARD": [pa.Pattern("CREDIT_CARD", r"\b(?:\d[ -]*?){13,16}\b", 0.5)], + "SSN": [pa.Pattern("SSN", r"\b\d{3}-?\d{2}-?\d{4}\b", 0.5)], + "PHONE": [pa.Pattern("PHONE", r"\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", 0.5)], + "EMAIL": [pa.Pattern("EMAIL", r"\S+@\S+", 0.5)], + } + + # create a list of pattern recognizers + @classmethod + def _create_pattern_recognizer(cls): + """ + For each entity, create a list of patterns to recognize it + + :param cls: PatternRecognizerFactory class + + :returns: List of pattern recognizers + """ + + # Entities to recognize and their regex patterns + + return [ + pa.PatternRecognizer(supported_entity=entity, patterns=pattern) + for entity, pattern in cls.RECOGNIZABLE_ENTITIES.items() + ]
    + + + +
    +[docs] +class CustomSpacyRecognizer(pa.LocalRecognizer): + """ + Custom Spacy Recognizer from Presidio Analyzer trained on Privy data. + The privy data is generated using this https://github.com/pixie-io/pixie/tree/main/src/datagen/pii/privy + It can be used to recognize custom entities, Since we want to use Presidio's Registries to generate AnalyzerEngine, + it inherits from Presidio Analyzer's LocalRecognizer class. + """ + + # Entities to recognize + + RECOGNIZABLE_ENTITIES = { + "LOCATION", + "PERSON", + "NRP", + "ORGANIZATION", + "DATE_TIME", + } + + # Default explanation for this recognizer + + _DEFAULT_EXPLANATION = ( + "Identified as {} by Spacy's Named Entity Recognition (Privy-trained)" + ) + + # Label groups to check + + _DEFAULT_CHECK_LABEL_GROUPS = [ + ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}), + ({"PERSON"}, {"PER", "PERSON"}), + ({"NRP"}, {"NORP", "NRP"}), + ({"ORGANIZATION"}, {"ORG"}), + ({"DATE_TIME"}, {"DATE_TIME"}), + ] + + # pretrained model for this recognizer + + _DEFAULT_MODEL_LANGUAGES = { + "en": "beki/en_spacy_pii_distilbert", + } + + _DEFAULT_PRESIDIO_EQUIVALENCES = { + "PER": "PERSON", + "LOC": "LOCATION", + "ORG": "ORGANIZATION", + "NROP": "NRP", + "DATE_TIME": "DATE_TIME", + } + + def __init__( + self, + supported_language: str = "en", + supported_entities: List[str] = None, + check_label_groups: Tuple[Set, Set] = None, + context: List[str] = None, + ner_strength: float = 1, + ): + """ + Initialize Spacy Recognizer. + + :param supported_language: Language to use, default is English + :param supported_entities: Entities to use for recognition + :param check_label_groups: Label groups to check for the entities + :param context: Context to use if any + :param ner_strength: Default confidence for NER prediction + + :returns: SpacyRecognizer object + """ + + # Default confidence for NER prediction + self.ner_strength = ner_strength + + self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS + supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES + super().__init__( + supported_entities=supported_entities, + supported_language=supported_language, + ) + + # get the presidio explanation for the result + + def _build_spacy_explanation( + self, original_score: float, explanation: str + ) -> pa.AnalysisExplanation: + """ + Create explanation for why this result was detected. + + :param original_score: Score given by this recognizer + :param explanation: Explanation string + + :returns: Presidio AnalysisExplanation object + """ + explanation = pa.AnalysisExplanation( + recognizer=self.__class__.__name__, + original_score=original_score, + textual_explanation=explanation, + ) + return explanation + + # main method for the recognizer +
    +[docs] + def analyze(self, text: str, entities: List[str], nlp_artifacts=None): # noqa D102 + """ + Analyze text using Spacy. + + :param text: Text to analyze + :param entities: Entities to analyze + :param nlp_artifacts: NLP artifacts to use + + :returns: List of Presidio RecognizerResult objects + """ + results = [] + if not nlp_artifacts: + logger.warning("Skipping SpaCy, nlp artifacts not provided...") + return results + + ner_entities = nlp_artifacts.entities + + # recognize the supported entities + for entity in entities: + if entity not in self.supported_entities: + continue + for ent in ner_entities: + if not self.__check_label(entity, ent.label_, self.check_label_groups): + continue + + # string of the explanation saying the entity is recognized by spacy + textual_explanation = self._DEFAULT_EXPLANATION.format(ent.label_) + explanation = self._build_spacy_explanation( + self.ner_strength, textual_explanation + ) + + # create the standard result with the entity, start, end, score, and explanation + spacy_result = pa.RecognizerResult( + entity_type=entity, + start=ent.start_char, + end=ent.end_char, + score=self.ner_strength, + analysis_explanation=explanation, + recognition_metadata={ + pa.RecognizerResult.RECOGNIZER_NAME_KEY: self.name + }, + ) + results.append(spacy_result) + + return results
    + + + @staticmethod + def __check_label( + entity: str, label: str, check_label_groups: Tuple[Set, Set] + ) -> bool: + """ + Check if the label is in the label group. + + :param entity: Entity to check + :param label: Label to check + :param check_label_groups: Label groups to check + + :returns: True if the label is in the label group, False otherwise + """ + return any( + entity in egrp and label in lgrp for egrp, lgrp in check_label_groups + )
    + + + +# Class to use Flair with Presidio as an external recognizer. +
    +[docs] +class FlairRecognizer(pa.EntityRecognizer): + """ + Wrapper for a flair model, if needed to be used within Presidio Analyzer. + This is to make sure the recognizer can be registered with Presidio registry. + """ + + RECOGNIZABLE_ENTITIES = { + "LOCATION", + "PERSON", + "NRP", + "GPE", + "ORGANIZATION", + "MAC_ADDRESS", + "US_BANK_NUMBER", + "IMEI", + "TITLE", + "LICENSE_PLATE", + "US_PASSPORT", + "CURRENCY", + "ROUTING_NUMBER", + "US_ITIN", + "US_BANK_NUMBER", + "US_DRIVER_LICENSE", + "AGE", + "PASSWORD", + "SWIFT_CODE", + } + + # This is used to construct the explanation for the result + + _DEFAULT_EXPLANATION = "Identified as {} by Flair's Named Entity Recognition" + + _DEFAULT_CHECK_LABEL_GROUPS = [ + ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}), + ({"PERSON"}, {"PER", "PERSON"}), + ({"NRP"}, {"NORP", "NRP"}), + ({"GPE"}, {"GPE"}), + ({"ORGANIZATION"}, {"ORG"}), + ({"MAC_ADDRESS"}, {"MAC_ADDRESS"}), + ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}), + ({"IMEI"}, {"IMEI"}), + ({"TITLE"}, {"TITLE"}), + ({"LICENSE_PLATE"}, {"LICENSE_PLATE"}), + ({"US_PASSPORT"}, {"US_PASSPORT"}), + ({"CURRENCY"}, {"CURRENCY"}), + ({"ROUTING_NUMBER"}, {"ROUTING_NUMBER"}), + ({"AGE"}, {"AGE"}), + ({"CURRENCY"}, {"CURRENCY"}), + ({"SWIFT_CODE"}, {"SWIFT_CODE"}), + ({"US_ITIN"}, {"US_ITIN"}), + ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}), + ({"US_DRIVER_LICENSE"}, {"US_DRIVER_LICENSE"}), + ] + + _DEFAULT_MODEL_LANGUAGES = { + "en": "beki/flair-pii-distilbert", + } + + _DEFAULT_PRESIDIO_EQUIVALENCES = { + "PER": "PERSON", + "LOC": "LOCATION", + "ORG": "ORGANIZATION", + "NROP": "NRP", + "URL": "URL", + "US_ITIN": "US_ITIN", + "US_PASSPORT": "US_PASSPORT", + "IBAN_CODE": "IBAN_CODE", + "IP_ADDRESS": "IP_ADDRESS", + "EMAIL_ADDRESS": "EMAIL", + "US_DRIVER_LICENSE": "US_DRIVER_LICENSE", + "US_BANK_NUMBER": "US_BANK_NUMBER", + } + + def __init__( + self, + supported_language: str = "en", + supported_entities: List[str] = None, + check_label_groups: Tuple[Set, Set] = None, + ): + """ + Initialize the FlairRecognizer. + + :param supported_language: Language to use + :param supported_entities: Entities to use + :param check_label_groups: Label groups to check + + :returns: FlairRecognizer object + + """ + self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS + + supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES + self.model = fl.models.SequenceTagger.load( + self._DEFAULT_MODEL_LANGUAGES.get(supported_language) + ) + + super().__init__( + supported_entities=supported_entities, + supported_language=supported_language, + name="Flair Analytics", + ) + + # main method for the recognizer +
    +[docs] + def analyze( + self, + text: str, + entities: List[str], + nlp_artifacts: pa.nlp_engine.NlpArtifacts = None, + ) -> List[pa.RecognizerResult]: + """ + Analyze text and return the results. + + :param text: The text for analysis. + :param entities: The list of entities to recognize. + :param nlp_artifacts: Not used by this recognizer but needed for the interface. + + :returns: The list of Presidio RecognizerResult constructed from the recognized Flair detections. + """ + + results = [] + + sentences = fl.data.Sentence(text) + self.model.predict(sentences) + + # If there are no specific list of entities, we will look for all of it. + if not entities: + entities = self.supported_entities + + # Go over the entities and check if they are in the supported entities list. + for entity in entities: + if entity not in self.supported_entities: + continue + + # Go over the sentences and check if the entity is in the sentence. + for ent in sentences.get_spans("ner"): + if not self.__check_label( + entity, ent.labels[0].value, self.check_label_groups + ): + continue + + # If the entity is in the sentence, we will add it to the results. + textual_explanation = self._DEFAULT_EXPLANATION.format( + ent.labels[0].value + ) + + # Build the explanation for the result + explanation = self._build_flair_explanation( + round(ent.score, 2), textual_explanation + ) + + flair_result = self._convert_to_recognizer_result(ent, explanation) + + results.append(flair_result) + + return results
    + + + def _convert_to_recognizer_result( + self, entity: fl.data.Span, explanation: str + ) -> pa.RecognizerResult: + """ + Convert Flair result to Presidio RecognizerResult. + + :param entity: Flair entity of Span + :param explanation: Presidio AnalysisExplanation + + :returns: Presidio RecognizerResult + """ + + # Convert the entity type to Presidio entity type + entity_type = self._DEFAULT_PRESIDIO_EQUIVALENCES.get(entity.tag, entity.tag) + + # Convert the score to Presidio score + flair_score = round(entity.score, 2) + + # Create the Presidio RecognizerResult from the Flair entity + flair_results = pa.RecognizerResult( + entity_type=entity_type, + start=entity.start_position, + end=entity.end_position, + score=flair_score, + analysis_explanation=explanation, + ) + + return flair_results + + def _build_flair_explanation( + self, original_score: float, explanation: str + ) -> pa.AnalysisExplanation: + """ + Create explanation for why this result was detected. + + :param original_score: Score given by this recognizer + :param explanation: Explanation string + + :returns: Presidio AnalysisExplanation + """ + + # Create the Presidio AnalysisExplanation for the result + explanation = pa.AnalysisExplanation( + recognizer=self.__class__.__name__, + original_score=original_score, + textual_explanation=explanation, + ) + return explanation + + # sanity check of the entity and label before recognition + @staticmethod + def __check_label( + entity: str, label: str, check_label_groups: Tuple[Set, Set] + ) -> bool: + return any( + entity in egrp and label in lgrp for egrp, lgrp in check_label_groups + )
    + + + +# get the analyzer engine based on the model +def _get_analyzer_engine( + model: str = None, entities: List[str] = None +) -> pa.AnalyzerEngine: + """ + Return pa.AnalyzerEngine. + + :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole". + :param entities: The list of entities to use. + + :returns: pa.AnalyzerEngine + """ + # recognizer registry that can store multiple recognizers + registry = pa.RecognizerRegistry() + if model == Models.SPACY: + # custom spacy recognizer + spacy_recognizer = CustomSpacyRecognizer() + # add the custom build spacy recognizer + registry.add_recognizer(spacy_recognizer) + elif model == Models.FLAIR: + # pre-trained flair recognizer + flair_recognizer = FlairRecognizer() + # add the custom build flair recognizer + registry.add_recognizer(flair_recognizer) + elif model == Models.PATTERN: + # add the pattern recognizer + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + elif model == Models.WHOLE: + spacy_recognizer = CustomSpacyRecognizer() + flair_recognizer = FlairRecognizer() + registry.add_recognizer(spacy_recognizer) + registry.add_recognizer(flair_recognizer) + # add the pattern recognizer + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + elif not model and entities: + if set(entities) & CustomSpacyRecognizer.RECOGNIZABLE_ENTITIES: + spacy_recognizer = CustomSpacyRecognizer() + registry.add_recognizer(spacy_recognizer) + if set(entities) & FlairRecognizer.RECOGNIZABLE_ENTITIES: + flair_recognizer = FlairRecognizer() + registry.add_recognizer(flair_recognizer) + # add the pattern recognizer + if set(entities) & (set(PatternRecognizerFactory.RECOGNIZABLE_ENTITIES.keys())): + pattern_recognizer_factory = PatternRecognizerFactory() + for recognizer in pattern_recognizer_factory._create_pattern_recognizer(): + registry.add_recognizer(recognizer) + else: + raise ValueError( + f"argument of model and entities can not be None at the same time" + ) + analyzer = pa.AnalyzerEngine( + registry=registry, + supported_languages=["en"], + ) + + supported_entities = analyzer.get_supported_entities() + + if entities and not all(item in supported_entities for item in entities): + not_supported_entities = [ + item for item in entities if item not in supported_entities + ] + raise ValueError( + f"The current model {model} doesn't support the following entities: {not_supported_entities}. " + f"Supported entities are: {supported_entities}" + ) + return analyzer + + +def _get_anonymizer_engine() -> pre_anoymizer.AnonymizerEngine: + """ + Return AnonymizerEngine. + + :returns: The AnonymizerEngine. + """ + return pre_anoymizer.AnonymizerEngine() + + +def _anonymize( + text: str, + analyze_results: List[pa.RecognizerResult], + entity_operator_map: dict = None, + is_full_text: bool = True, +) -> str: + """ + Anonymize identified input using Presidio Abonymizer. + + :param text: The text for analysis. + :param analyze_results: The list of Presidio RecognizerResult constructed from + :param entity_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params. + :param is_full_text: Whether the text is full text or not. + + :returns: The anonymized text. + """ + if not text: + return "" + + anonymizer_engine = _get_anonymizer_engine() + if not entity_operator_map: + operators = None + else: + # Create OperatorConfig based on the entity_operator_map + operators = { + entity: OperatorConfig(operator_name, operator_params) + for entity, (operator_name, operator_params) in entity_operator_map.items() + } + + if is_full_text: + # Anonymize the entire text + return anonymizer_engine.anonymize( + text=text, analyzer_results=analyze_results, operators=operators + ).text + # Tokenize the text to sentences + sentences = nltk.sent_tokenize(text) + anonymized_sentences = [] + current_idx = 0 + + # Find the sentence that has pii entity + for sentence in sentences: + start_idx = current_idx + end_idx = start_idx + len(sentence) + + # Get the entities that are in the sentence, update hte start_idx and end_idx + sentence_results = [ + pa.RecognizerResult( + result.entity_type, + start=result.start - start_idx, + end=result.end - start_idx, + score=result.score, + ) + for result in analyze_results + if result.start >= start_idx and result.end <= end_idx + ] + + # If PII is detected + if sentence_results: + anonymized_sentence = anonymizer_engine.anonymize( + text=sentence, analyzer_results=sentence_results, operators=operators + ).text + anonymized_sentences.append(anonymized_sentence) + + current_idx = end_idx + + return " ".join(anonymized_sentences) + + +def _get_tokens( + text: str, analyze_results: List[pa.RecognizerResult], is_full: bool = True +) -> List[str]: + """ + Get the full tokens or only contains the entities that can form a sentence. + + :param text: The text for analysis. + :param analyze_results: The list of Presidio RecognizerResult constructed from + :param is_full: Whether return full tokens or just the tokens that only contains the entities that can form a sentence. + + :returns: The tokens. + """ + + tokens = [] + # sort by start index + results = sorted(analyze_results, key=lambda x: x.start) + for i, res in enumerate(results): + if i == 0: + tokens.append(text[: res.start]) + + # append entity text and entity type + tokens.append((text[res.start : res.end], res.entity_type)) + + # if another entity coming i.e. we're not at the last results element, + # add text up to next entity + if i != len(results) - 1: + tokens.append(text[res.end : results[i + 1].start]) + # if no more entities coming, add all remaining text + else: + tokens.append(text[res.end :]) + + # get the tokens that only contains the entities that can form a sentence + part_annontated_tokens = [] + if not is_full: + last_end_sentence = 0 + for i, token in enumerate(tokens): + if any(item in token for item in [".", "!", "?"]) and any( + type(item) is tuple for item in tokens[last_end_sentence:i] + ): + part_annontated_tokens.append(tokens[last_end_sentence:i]) + last_end_sentence = i + return part_annontated_tokens + return tokens + + +def _annotate( + text: str, st_analyze_results: List[pa.RecognizerResult], is_full_html: bool = True +) -> List[str]: + """ + Annotate identified input using Presidio Anonymizer. + + :param text: The text for analysis. + :param st_analyze_results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The list of tokens with the identified entities. + + """ + return _get_tokens(text, st_analyze_results, is_full_html) + + +def _process( + text: str, + model: pa.AnalyzerEngine, + score_threshold: float, + entities: List[str] = None, + entities_operator_map: dict = None, + is_full_text: bool = True, +) -> Tuple[str, list]: + """ + Process the text of str using the model. + + :param text: Text to process + :param model: Model to use for processing + :param entities: Entities to recognize + :param entities_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params. + :param score_threshold: The score threshold to use for recognition + :param is_full_text: Whether to return the full text or just the annotated text + + :returns: A tuple of: + + * the anonymized text + * the list of Presidio RecognizerResult constructed from analysis + """ + + # get the analyzer engine + analyzer = model + + # analyze the text that can be used for anonymization + results = analyzer.analyze( + text=text, + language="en", + entities=entities, + score_threshold=score_threshold, + return_decision_process=True, + ) + + # anonymize the text, replace the pii entities with the labels + anonymized_text = _anonymize(text, results, entities_operator_map, is_full_text) + + return anonymized_text, results + + +def _get_single_html( + text: str, results: List[pa.RecognizerResult], is_full_html: bool = True +): + """ + Generate the html for a single txt file. + + :param text: The text for analysis. + :param results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The html string for a single txt file. + """ + # convert the results to tokens to generate the html + tokens = _annotate(text, results, is_full_html) + html = at_util.get_annotated_html(*tokens) + + # avoid the error during rendering of the \n in the html + backslash_char = "\\" + + html_str = f"<p>{html.replace('{backslash_char}n', '<br>')}</p>" + + return html_str + + +def _get_single_json(results: List[pa.RecognizerResult], is_full_report: bool = True): + """ + Generate the json for a single txt file. + + :param results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full json or not. + + :returns: The json string for a single txt file. + """ + # generate the stats report if needed + if not is_full_report: + stats = [] + # add the simplify stats logic here + for item in results: + item.analysis_explanation = None + stats.append(item) + else: + stats = results + + return stats + + +def _get_all_html( + txt_content: dict, + res_dict: dict, + is_full_html: bool = True, +): + """ + Generate the html for all txt files. + + :param txt_content: The dictionary of txt file name and content. + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The html string for all txt files. + + """ + # These are placeholder for the html string + html_index = "<html><head><title>Highlighted Pii Entities</title></head><body><h1>Highlighted Pii Entities</h1><ul>" + html_content = "" + for txt_file, results in res_dict.items(): + txt = txt_content[txt_file] + html_index += f"<li><a href='#{txt_file}'>{txt_file}</a></li>" + html_content += f"<li><h2>{txt_file}</h2><p>{_get_single_html(txt, results, is_full_html)}</p></li>" + html_index += "</ul>" + html_res = f"{html_index}{html_content}</body></html>" + + return html_res + + +def _get_all_rpt(res_dict: dict, is_full_report: bool = True): + """ + Generate the stats report for all txt files. + + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full report or not. + + :returns: The stats report for all txt files. + """ + # These are placeholder for the json report + stats_dict = {} + for txt_file, results in res_dict.items(): + new_stats = [] + for item in _get_single_json(results, is_full_report): + if is_full_report: + item.analysis_explanation = item.analysis_explanation.to_dict() + new_stats.append(item.to_dict()) + else: + tmp_dict = item.to_dict() + tmp_dict.pop("analysis_explanation") + tmp_dict.pop("recognition_metadata") + new_stats.append(tmp_dict) + stats_dict[txt_file] = new_stats + return stats_dict + + +
    +[docs] +def recognize_pii( + context: mlrun.MLClientCtx, + input_path: Union[str, pathlib.Path], + html_key: str, + score_threshold: float, + output_directory: str = None, + entities: List[ + str + ] = None, # List of entities to recognize, default is recognizing all + entity_operator_map: dict = None, + model: str = None, + generate_json: bool = True, + generate_html: bool = True, + is_full_text: bool = True, + is_full_html: bool = True, + is_full_report: bool = True, +) -> Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, dict]]: + """ + Walk through the input path, recognize PII in text and store the anonymized text in the output path. + Generate the html with different colors for each entity, json report of the explanation. + + :param context: The MLRun context. this is needed for log the artifacts. + :param input_path: The input path of the text files needs to be analyzed. + :param html_key: The html key for the artifact. + :param score_threshold: The score threshold to mark the recognition as trusted. + :param output_directory: The output directory path to store the anonymized text. + :param entities: The list of entities to recognize. + :param entity_operator_map: The map of entity to operator (mask, redact, replace, keep, hash, and its params) + :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole". + :param generate_json: Whether to generate the json report of the explanation. + :param generate_html: Whether to generate the html report of the explanation. + :param is_full_text: Whether to return the full text or only the masked text. + :param is_full_html: Whether to return the full html or just the annotated text + :param is_full_report: Whether to return the full report or just the score and start, end index + + :returns: A tuple of: + + * Path to the output directory + * The json report of the explanation (if generate_json is True) + * A dictionary of errors files that were not processed + + """ + + # Set output directory + if output_directory is None: + output_directory = tempfile.mkdtemp() + + # Create the output directory: + output_directory = pathlib.Path(output_directory) + if not output_directory.exists(): + output_directory.mkdir(parents=True, exist_ok=True) + + txt_files_directory = pathlib.Path(input_path) + successes = [] + errors = {} + + res_dict = {} + txt_content = {} + # Load the model: + analyzer = _get_analyzer_engine(model, entities) + logger.info("Model loaded") + # Go over the text files in the input path, analyze and anonymize them: + for txt_file in tqdm( + list(txt_files_directory.glob("*.txt")), + desc="Processing files", + unit="file", + ): + try: + # Load the str from the text file + text = txt_file.read_text() + txt_content[str(txt_file)] = text + # Process the text to recoginze the pii entities in it + anonymized_text, results = _process( + text=text, + model=analyzer, + entities=entities, + entities_operator_map=entity_operator_map, + score_threshold=score_threshold, + is_full_text=is_full_text, + ) + res_dict[str(txt_file)] = results + # Store the anonymized text in the output path + output_file = output_directory / f"{txt_file.stem}.txt" + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + f.write(anonymized_text) + successes.append([txt_file.name, output_file.name]) + except Exception as e: + errors[str(txt_file)] = str(e) + logger.error(f"Error processing {txt_file}: {e}") + + successes = pd.DataFrame( + successes, + columns=["original_file", "anonymized_file"], + ) + + if generate_html: + # Generate the html report + html_res = _get_all_html(txt_content, res_dict, is_full_html) + # Store the html report in the context + arti_html = mlrun.artifacts.Artifact(body=html_res, format="html", key=html_key) + context.log_artifact(arti_html) + if generate_json: + # Generate the json report + json_res = _get_all_rpt(res_dict, is_full_report) + return str(output_directory), successes, errors, json_res + return str(output_directory), successes, errors
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/0.4.0/static/source.html b/functions/master/pii_recognizer/0.4.0/static/source.html new file mode 100644 index 00000000..dde9a412 --- /dev/null +++ b/functions/master/pii_recognizer/0.4.0/static/source.html @@ -0,0 +1,986 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import logging
    +import os
    +import pathlib
    +import tempfile
    +import warnings
    +from typing import List, Set, Tuple, Union
    +
    +import annotated_text.util as at_util
    +import mlrun
    +import nltk
    +import pandas as pd
    +import presidio_analyzer as pa
    +import presidio_anonymizer as pre_anoymizer
    +from presidio_anonymizer.entities import OperatorConfig
    +from tqdm import tqdm
    +
    +try:
    +    import flair as fl
    +except ModuleNotFoundError:
    +    print("Flair is not installed")
    +
    +# There is a conflict between Rust-based tokenizers' parallel processing
    +# and Python's fork operations during multiprocessing. To avoid this, we need
    +# the following two lines
    +
    +os.environ["TOKENIZERS_PARALLELISM"] = "false"
    +warnings.filterwarnings("ignore")
    +
    +logger = logging.getLogger("pii-recognizer")
    +
    +
    +# Add the constant classes of Models and Entities to govern the whole package
    +class Models:
    +    WHOLE = "whole"
    +    PATTERN = "pattern"
    +    SPACY = "spacy"
    +    FLAIR = "flair"
    +
    +
    +class Entities:
    +    CREDIT_CARD = "CREDIT_CARD"
    +    SSN = "SSN"
    +    PHONE = "PHONE"
    +    EMAIL = "EMAIL"
    +    LOCATION = "LOCATION"
    +    PERSON = "PERSON"
    +    NRP = "NRP"
    +    ORGANIZATION = "ORGANIZATION"
    +    DATE_TIME = "DATE_TIME"
    +    GPE = ("GPE",)
    +    MAC_ADDRESS = "MAC_ADDRESS"
    +    US_BANK_NUMBER = "US_BANK_NUMBER"
    +    IMEI = "IMEI"
    +    TITLE = "TITLE"
    +    LICENSE_PLATE = "LICENSE_PLATE"
    +    US_PASSPORT = "US_PASSPORT"
    +    CURRENCY = "CURRENCY"
    +    ROUTING_NUMBER = "ROUTING_NUMBER"
    +    US_ITIN = "US_ITIN"
    +    US_BANK_NUMBER = "US_BANK_NUMBER"
    +    US_DRIVER_LICENSE = "US_DRIVER_LICENSE"
    +    AGE = "AGE"
    +    PASSWORD = "PASSWORD"
    +    SWIFT_CODE = "SWIFT_CODE"
    +
    +
    +class PatternRecognizerFactory:
    +    """
    +    Factory for creating pattern recognizers, it can be extended in the future to
    +    add more regex pattern for different entities. For the pattern recognizer to work,
    +    we need construct a list of regex patterns for each entity.
    +    """
    +
    +    RECOGNIZABLE_ENTITIES = {
    +        "CREDIT_CARD": [pa.Pattern("CREDIT_CARD", r"\b(?:\d[ -]*?){13,16}\b", 0.5)],
    +        "SSN": [pa.Pattern("SSN", r"\b\d{3}-?\d{2}-?\d{4}\b", 0.5)],
    +        "PHONE": [pa.Pattern("PHONE", r"\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", 0.5)],
    +        "EMAIL": [pa.Pattern("EMAIL", r"\S+@\S+", 0.5)],
    +    }
    +
    +    # create a list of pattern recognizers
    +    @classmethod
    +    def _create_pattern_recognizer(cls):
    +        """
    +        For each entity, create a list of patterns to recognize it
    +
    +        :param cls: PatternRecognizerFactory class
    +
    +        :returns: List of pattern recognizers
    +        """
    +
    +        # Entities to recognize and their regex patterns
    +
    +        return [
    +            pa.PatternRecognizer(supported_entity=entity, patterns=pattern)
    +            for entity, pattern in cls.RECOGNIZABLE_ENTITIES.items()
    +        ]
    +
    +
    +class CustomSpacyRecognizer(pa.LocalRecognizer):
    +    """
    +    Custom Spacy Recognizer from Presidio Analyzer trained on Privy data.
    +    The privy data is generated using this https://github.com/pixie-io/pixie/tree/main/src/datagen/pii/privy
    +    It can be used to recognize custom entities, Since we want to use Presidio's Registries to generate AnalyzerEngine,
    +    it inherits from Presidio Analyzer's LocalRecognizer class.
    +    """
    +
    +    # Entities to recognize
    +
    +    RECOGNIZABLE_ENTITIES = {
    +        "LOCATION",
    +        "PERSON",
    +        "NRP",
    +        "ORGANIZATION",
    +        "DATE_TIME",
    +    }
    +
    +    # Default explanation for this recognizer
    +
    +    _DEFAULT_EXPLANATION = (
    +        "Identified as {} by Spacy's Named Entity Recognition (Privy-trained)"
    +    )
    +
    +    # Label groups to check
    +
    +    _DEFAULT_CHECK_LABEL_GROUPS = [
    +        ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}),
    +        ({"PERSON"}, {"PER", "PERSON"}),
    +        ({"NRP"}, {"NORP", "NRP"}),
    +        ({"ORGANIZATION"}, {"ORG"}),
    +        ({"DATE_TIME"}, {"DATE_TIME"}),
    +    ]
    +
    +    # pretrained model for this recognizer
    +
    +    _DEFAULT_MODEL_LANGUAGES = {
    +        "en": "beki/en_spacy_pii_distilbert",
    +    }
    +
    +    _DEFAULT_PRESIDIO_EQUIVALENCES = {
    +        "PER": "PERSON",
    +        "LOC": "LOCATION",
    +        "ORG": "ORGANIZATION",
    +        "NROP": "NRP",
    +        "DATE_TIME": "DATE_TIME",
    +    }
    +
    +    def __init__(
    +        self,
    +        supported_language: str = "en",
    +        supported_entities: List[str] = None,
    +        check_label_groups: Tuple[Set, Set] = None,
    +        context: List[str] = None,
    +        ner_strength: float = 1,
    +    ):
    +        """
    +        Initialize Spacy Recognizer.
    +
    +        :param supported_language: Language to use, default is English
    +        :param supported_entities: Entities to use for recognition
    +        :param check_label_groups: Label groups to check for the entities
    +        :param context:            Context to use if any
    +        :param ner_strength:       Default confidence for NER prediction
    +
    +        :returns: SpacyRecognizer object
    +        """
    +
    +        # Default confidence for NER prediction
    +        self.ner_strength = ner_strength
    +
    +        self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS
    +        supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES
    +        super().__init__(
    +            supported_entities=supported_entities,
    +            supported_language=supported_language,
    +        )
    +
    +    # get the presidio explanation for the result
    +
    +    def _build_spacy_explanation(
    +        self, original_score: float, explanation: str
    +    ) -> pa.AnalysisExplanation:
    +        """
    +        Create explanation for why this result was detected.
    +
    +        :param original_score: Score given by this recognizer
    +        :param explanation:    Explanation string
    +
    +        :returns: Presidio AnalysisExplanation object
    +        """
    +        explanation = pa.AnalysisExplanation(
    +            recognizer=self.__class__.__name__,
    +            original_score=original_score,
    +            textual_explanation=explanation,
    +        )
    +        return explanation
    +
    +    # main method for the recognizer
    +    def analyze(self, text: str, entities: List[str], nlp_artifacts=None):  # noqa D102
    +        """
    +        Analyze text using Spacy.
    +
    +        :param text:          Text to analyze
    +        :param entities:      Entities to analyze
    +        :param nlp_artifacts: NLP artifacts to use
    +
    +        :returns: List of Presidio RecognizerResult objects
    +        """
    +        results = []
    +        if not nlp_artifacts:
    +            logger.warning("Skipping SpaCy, nlp artifacts not provided...")
    +            return results
    +
    +        ner_entities = nlp_artifacts.entities
    +
    +        # recognize the supported entities
    +        for entity in entities:
    +            if entity not in self.supported_entities:
    +                continue
    +            for ent in ner_entities:
    +                if not self.__check_label(entity, ent.label_, self.check_label_groups):
    +                    continue
    +
    +                # string of the explanation saying the entity is recognized by spacy
    +                textual_explanation = self._DEFAULT_EXPLANATION.format(ent.label_)
    +                explanation = self._build_spacy_explanation(
    +                    self.ner_strength, textual_explanation
    +                )
    +
    +                # create the standard result with the entity, start, end, score, and explanation
    +                spacy_result = pa.RecognizerResult(
    +                    entity_type=entity,
    +                    start=ent.start_char,
    +                    end=ent.end_char,
    +                    score=self.ner_strength,
    +                    analysis_explanation=explanation,
    +                    recognition_metadata={
    +                        pa.RecognizerResult.RECOGNIZER_NAME_KEY: self.name
    +                    },
    +                )
    +                results.append(spacy_result)
    +
    +        return results
    +
    +    @staticmethod
    +    def __check_label(
    +        entity: str, label: str, check_label_groups: Tuple[Set, Set]
    +    ) -> bool:
    +        """
    +        Check if the label is in the label group.
    +
    +        :param entity:             Entity to check
    +        :param label:              Label to check
    +        :param check_label_groups: Label groups to check
    +
    +        :returns: True if the label is in the label group, False otherwise
    +        """
    +        return any(
    +            entity in egrp and label in lgrp for egrp, lgrp in check_label_groups
    +        )
    +
    +
    +# Class to use Flair with Presidio as an external recognizer.
    +class FlairRecognizer(pa.EntityRecognizer):
    +    """
    +    Wrapper for a flair model, if needed to be used within Presidio Analyzer.
    +    This is to make sure the recognizer can be registered with Presidio registry.
    +    """
    +
    +    RECOGNIZABLE_ENTITIES = {
    +        "LOCATION",
    +        "PERSON",
    +        "NRP",
    +        "GPE",
    +        "ORGANIZATION",
    +        "MAC_ADDRESS",
    +        "US_BANK_NUMBER",
    +        "IMEI",
    +        "TITLE",
    +        "LICENSE_PLATE",
    +        "US_PASSPORT",
    +        "CURRENCY",
    +        "ROUTING_NUMBER",
    +        "US_ITIN",
    +        "US_BANK_NUMBER",
    +        "US_DRIVER_LICENSE",
    +        "AGE",
    +        "PASSWORD",
    +        "SWIFT_CODE",
    +    }
    +
    +    # This is used to construct the explanation for the result
    +
    +    _DEFAULT_EXPLANATION = "Identified as {} by Flair's Named Entity Recognition"
    +
    +    _DEFAULT_CHECK_LABEL_GROUPS = [
    +        ({"LOCATION"}, {"LOC", "LOCATION", "STREET_ADDRESS", "COORDINATE"}),
    +        ({"PERSON"}, {"PER", "PERSON"}),
    +        ({"NRP"}, {"NORP", "NRP"}),
    +        ({"GPE"}, {"GPE"}),
    +        ({"ORGANIZATION"}, {"ORG"}),
    +        ({"MAC_ADDRESS"}, {"MAC_ADDRESS"}),
    +        ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}),
    +        ({"IMEI"}, {"IMEI"}),
    +        ({"TITLE"}, {"TITLE"}),
    +        ({"LICENSE_PLATE"}, {"LICENSE_PLATE"}),
    +        ({"US_PASSPORT"}, {"US_PASSPORT"}),
    +        ({"CURRENCY"}, {"CURRENCY"}),
    +        ({"ROUTING_NUMBER"}, {"ROUTING_NUMBER"}),
    +        ({"AGE"}, {"AGE"}),
    +        ({"CURRENCY"}, {"CURRENCY"}),
    +        ({"SWIFT_CODE"}, {"SWIFT_CODE"}),
    +        ({"US_ITIN"}, {"US_ITIN"}),
    +        ({"US_BANK_NUMBER"}, {"US_BANK_NUMBER"}),
    +        ({"US_DRIVER_LICENSE"}, {"US_DRIVER_LICENSE"}),
    +    ]
    +
    +    _DEFAULT_MODEL_LANGUAGES = {
    +        "en": "beki/flair-pii-distilbert",
    +    }
    +
    +    _DEFAULT_PRESIDIO_EQUIVALENCES = {
    +        "PER": "PERSON",
    +        "LOC": "LOCATION",
    +        "ORG": "ORGANIZATION",
    +        "NROP": "NRP",
    +        "URL": "URL",
    +        "US_ITIN": "US_ITIN",
    +        "US_PASSPORT": "US_PASSPORT",
    +        "IBAN_CODE": "IBAN_CODE",
    +        "IP_ADDRESS": "IP_ADDRESS",
    +        "EMAIL_ADDRESS": "EMAIL",
    +        "US_DRIVER_LICENSE": "US_DRIVER_LICENSE",
    +        "US_BANK_NUMBER": "US_BANK_NUMBER",
    +    }
    +
    +    def __init__(
    +        self,
    +        supported_language: str = "en",
    +        supported_entities: List[str] = None,
    +        check_label_groups: Tuple[Set, Set] = None,
    +    ):
    +        """
    +        Initialize the FlairRecognizer.
    +
    +        :param supported_language: Language to use
    +        :param supported_entities: Entities to use
    +        :param check_label_groups: Label groups to check
    +
    +        :returns: FlairRecognizer object
    +
    +        """
    +        self.check_label_groups = check_label_groups or self._DEFAULT_CHECK_LABEL_GROUPS
    +
    +        supported_entities = supported_entities or self.RECOGNIZABLE_ENTITIES
    +        self.model = fl.models.SequenceTagger.load(
    +            self._DEFAULT_MODEL_LANGUAGES.get(supported_language)
    +        )
    +
    +        super().__init__(
    +            supported_entities=supported_entities,
    +            supported_language=supported_language,
    +            name="Flair Analytics",
    +        )
    +
    +    # main method for the recognizer
    +    def analyze(
    +        self,
    +        text: str,
    +        entities: List[str],
    +        nlp_artifacts: pa.nlp_engine.NlpArtifacts = None,
    +    ) -> List[pa.RecognizerResult]:
    +        """
    +        Analyze text and return the results.
    +
    +        :param text:          The text for analysis.
    +        :param entities:      The list of entities to recognize.
    +        :param nlp_artifacts: Not used by this recognizer but needed for the interface.
    +
    +        :returns: The list of Presidio RecognizerResult constructed from the recognized Flair detections.
    +        """
    +
    +        results = []
    +
    +        sentences = fl.data.Sentence(text)
    +        self.model.predict(sentences)
    +
    +        # If there are no specific list of entities, we will look for all of it.
    +        if not entities:
    +            entities = self.supported_entities
    +
    +        # Go over the entities and check if they are in the supported entities list.
    +        for entity in entities:
    +            if entity not in self.supported_entities:
    +                continue
    +
    +            # Go over the sentences and check if the entity is in the sentence.
    +            for ent in sentences.get_spans("ner"):
    +                if not self.__check_label(
    +                    entity, ent.labels[0].value, self.check_label_groups
    +                ):
    +                    continue
    +
    +                # If the entity is in the sentence, we will add it to the results.
    +                textual_explanation = self._DEFAULT_EXPLANATION.format(
    +                    ent.labels[0].value
    +                )
    +
    +                # Build the explanation for the result
    +                explanation = self._build_flair_explanation(
    +                    round(ent.score, 2), textual_explanation
    +                )
    +
    +                flair_result = self._convert_to_recognizer_result(ent, explanation)
    +
    +                results.append(flair_result)
    +
    +        return results
    +
    +    def _convert_to_recognizer_result(
    +        self, entity: fl.data.Span, explanation: str
    +    ) -> pa.RecognizerResult:
    +        """
    +        Convert Flair result to Presidio RecognizerResult.
    +
    +        :param entity:      Flair entity of Span
    +        :param explanation: Presidio AnalysisExplanation
    +
    +        :returns: Presidio RecognizerResult
    +        """
    +
    +        # Convert the entity type to Presidio entity type
    +        entity_type = self._DEFAULT_PRESIDIO_EQUIVALENCES.get(entity.tag, entity.tag)
    +
    +        # Convert the score to Presidio score
    +        flair_score = round(entity.score, 2)
    +
    +        # Create the Presidio RecognizerResult from the Flair entity
    +        flair_results = pa.RecognizerResult(
    +            entity_type=entity_type,
    +            start=entity.start_position,
    +            end=entity.end_position,
    +            score=flair_score,
    +            analysis_explanation=explanation,
    +        )
    +
    +        return flair_results
    +
    +    def _build_flair_explanation(
    +        self, original_score: float, explanation: str
    +    ) -> pa.AnalysisExplanation:
    +        """
    +        Create explanation for why this result was detected.
    +
    +        :param original_score: Score given by this recognizer
    +        :param explanation:    Explanation string
    +
    +        :returns: Presidio AnalysisExplanation
    +        """
    +
    +        # Create the Presidio AnalysisExplanation for the result
    +        explanation = pa.AnalysisExplanation(
    +            recognizer=self.__class__.__name__,
    +            original_score=original_score,
    +            textual_explanation=explanation,
    +        )
    +        return explanation
    +
    +    # sanity check of the entity and label before recognition
    +    @staticmethod
    +    def __check_label(
    +        entity: str, label: str, check_label_groups: Tuple[Set, Set]
    +    ) -> bool:
    +        return any(
    +            entity in egrp and label in lgrp for egrp, lgrp in check_label_groups
    +        )
    +
    +
    +# get the analyzer engine based on the model
    +def _get_analyzer_engine(
    +    model: str = None, entities: List[str] = None
    +) -> pa.AnalyzerEngine:
    +    """
    +    Return pa.AnalyzerEngine.
    +
    +    :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole".
    +    :param entities: The list of entities to use.
    +
    +    :returns: pa.AnalyzerEngine
    +    """
    +    # recognizer registry that can store multiple recognizers
    +    registry = pa.RecognizerRegistry()
    +    if model == Models.SPACY:
    +        # custom spacy recognizer
    +        spacy_recognizer = CustomSpacyRecognizer()
    +        # add the custom build spacy recognizer
    +        registry.add_recognizer(spacy_recognizer)
    +    elif model == Models.FLAIR:
    +        # pre-trained flair recognizer
    +        flair_recognizer = FlairRecognizer()
    +        # add the custom build flair recognizer
    +        registry.add_recognizer(flair_recognizer)
    +    elif model == Models.PATTERN:
    +        # add the pattern recognizer
    +        pattern_recognizer_factory = PatternRecognizerFactory()
    +        for recognizer in pattern_recognizer_factory._create_pattern_recognizer():
    +            registry.add_recognizer(recognizer)
    +    elif model == Models.WHOLE:
    +        spacy_recognizer = CustomSpacyRecognizer()
    +        flair_recognizer = FlairRecognizer()
    +        registry.add_recognizer(spacy_recognizer)
    +        registry.add_recognizer(flair_recognizer)
    +        # add the pattern recognizer
    +        pattern_recognizer_factory = PatternRecognizerFactory()
    +        for recognizer in pattern_recognizer_factory._create_pattern_recognizer():
    +            registry.add_recognizer(recognizer)
    +    elif not model and entities:
    +        if set(entities) & CustomSpacyRecognizer.RECOGNIZABLE_ENTITIES:
    +            spacy_recognizer = CustomSpacyRecognizer()
    +            registry.add_recognizer(spacy_recognizer)
    +        if set(entities) & FlairRecognizer.RECOGNIZABLE_ENTITIES:
    +            flair_recognizer = FlairRecognizer()
    +            registry.add_recognizer(flair_recognizer)
    +        # add the pattern recognizer
    +        if set(entities) & (set(PatternRecognizerFactory.RECOGNIZABLE_ENTITIES.keys())):
    +            pattern_recognizer_factory = PatternRecognizerFactory()
    +            for recognizer in pattern_recognizer_factory._create_pattern_recognizer():
    +                registry.add_recognizer(recognizer)
    +    else:
    +        raise ValueError(
    +            f"argument of model and entities can not be None at the same time"
    +        )
    +    analyzer = pa.AnalyzerEngine(
    +        registry=registry,
    +        supported_languages=["en"],
    +    )
    +
    +    supported_entities = analyzer.get_supported_entities()
    +
    +    if entities and not all(item in supported_entities for item in entities):
    +        not_supported_entities = [
    +            item for item in entities if item not in supported_entities
    +        ]
    +        raise ValueError(
    +            f"The current model {model} doesn't support the following entities: {not_supported_entities}. "
    +            f"Supported entities are: {supported_entities}"
    +        )
    +    return analyzer
    +
    +
    +def _get_anonymizer_engine() -> pre_anoymizer.AnonymizerEngine:
    +    """
    +    Return AnonymizerEngine.
    +
    +    :returns: The AnonymizerEngine.
    +    """
    +    return pre_anoymizer.AnonymizerEngine()
    +
    +
    +def _anonymize(
    +    text: str,
    +    analyze_results: List[pa.RecognizerResult],
    +    entity_operator_map: dict = None,
    +    is_full_text: bool = True,
    +) -> str:
    +    """
    +    Anonymize identified input using Presidio Abonymizer.
    +
    +    :param text:                The text for analysis.
    +    :param analyze_results:     The list of Presidio RecognizerResult constructed from
    +    :param entity_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params.
    +    :param is_full_text:        Whether the text is full text or not.
    +
    +    :returns: The anonymized text.
    +    """
    +    if not text:
    +        return ""
    +
    +    anonymizer_engine = _get_anonymizer_engine()
    +    if not entity_operator_map:
    +        operators = None
    +    else:
    +        # Create OperatorConfig based on the entity_operator_map
    +        operators = {
    +            entity: OperatorConfig(operator_name, operator_params)
    +            for entity, (operator_name, operator_params) in entity_operator_map.items()
    +        }
    +
    +    if is_full_text:
    +        # Anonymize the entire text
    +        return anonymizer_engine.anonymize(
    +            text=text, analyzer_results=analyze_results, operators=operators
    +        ).text
    +    # Tokenize the text to sentences
    +    sentences = nltk.sent_tokenize(text)
    +    anonymized_sentences = []
    +    current_idx = 0
    +
    +    # Find the sentence that has pii entity
    +    for sentence in sentences:
    +        start_idx = current_idx
    +        end_idx = start_idx + len(sentence)
    +
    +        # Get the entities that are in the sentence, update hte start_idx and end_idx
    +        sentence_results = [
    +            pa.RecognizerResult(
    +                result.entity_type,
    +                start=result.start - start_idx,
    +                end=result.end - start_idx,
    +                score=result.score,
    +            )
    +            for result in analyze_results
    +            if result.start >= start_idx and result.end <= end_idx
    +        ]
    +
    +        # If PII is detected
    +        if sentence_results:
    +            anonymized_sentence = anonymizer_engine.anonymize(
    +                text=sentence, analyzer_results=sentence_results, operators=operators
    +            ).text
    +            anonymized_sentences.append(anonymized_sentence)
    +
    +        current_idx = end_idx
    +
    +    return " ".join(anonymized_sentences)
    +
    +
    +def _get_tokens(
    +    text: str, analyze_results: List[pa.RecognizerResult], is_full: bool = True
    +) -> List[str]:
    +    """
    +    Get the full tokens or only contains the entities that can form a sentence.
    +
    +    :param text:            The text for analysis.
    +    :param analyze_results: The list of Presidio RecognizerResult constructed from
    +    :param is_full:         Whether return full tokens or just the tokens that only contains the entities that can form a sentence.
    +
    +    :returns: The tokens.
    +    """
    +
    +    tokens = []
    +    # sort by start index
    +    results = sorted(analyze_results, key=lambda x: x.start)
    +    for i, res in enumerate(results):
    +        if i == 0:
    +            tokens.append(text[: res.start])
    +
    +        # append entity text and entity type
    +        tokens.append((text[res.start : res.end], res.entity_type))
    +
    +        # if another entity coming i.e. we're not at the last results element,
    +        # add text up to next entity
    +        if i != len(results) - 1:
    +            tokens.append(text[res.end : results[i + 1].start])
    +        # if no more entities coming, add all remaining text
    +        else:
    +            tokens.append(text[res.end :])
    +
    +    # get the tokens that only contains the entities that can form a sentence
    +    part_annontated_tokens = []
    +    if not is_full:
    +        last_end_sentence = 0
    +        for i, token in enumerate(tokens):
    +            if any(item in token for item in [".", "!", "?"]) and any(
    +                type(item) is tuple for item in tokens[last_end_sentence:i]
    +            ):
    +                part_annontated_tokens.append(tokens[last_end_sentence:i])
    +                last_end_sentence = i
    +        return part_annontated_tokens
    +    return tokens
    +
    +
    +def _annotate(
    +    text: str, st_analyze_results: List[pa.RecognizerResult], is_full_html: bool = True
    +) -> List[str]:
    +    """
    +    Annotate identified input using Presidio Anonymizer.
    +
    +    :param text:               The text for analysis.
    +    :param st_analyze_results: The list of Presidio RecognizerResult constructed from analysis.
    +    :param is_full_html:       Whether generate full html or not.
    +
    +    :returns: The list of tokens with the identified entities.
    +
    +    """
    +    return _get_tokens(text, st_analyze_results, is_full_html)
    +
    +
    +def _process(
    +    text: str,
    +    model: pa.AnalyzerEngine,
    +    score_threshold: float,
    +    entities: List[str] = None,
    +    entities_operator_map: dict = None,
    +    is_full_text: bool = True,
    +) -> Tuple[str, list]:
    +    """
    +    Process the text of str using the model.
    +
    +    :param text:                  Text to process
    +    :param model:                 Model to use for processing
    +    :param entities:              Entities to recognize
    +    :param entities_operator_map: The entity_operator_map is a dictionary that maps entity to operator name and operator params.
    +    :param score_threshold:       The score threshold to use for recognition
    +    :param is_full_text:          Whether to return the full text or just the annotated text
    +
    +    :returns: A tuple of:
    +
    +              * the anonymized text
    +              * the list of Presidio RecognizerResult constructed from analysis
    +    """
    +
    +    # get the analyzer engine
    +    analyzer = model
    +
    +    # analyze the text that can be used for anonymization
    +    results = analyzer.analyze(
    +        text=text,
    +        language="en",
    +        entities=entities,
    +        score_threshold=score_threshold,
    +        return_decision_process=True,
    +    )
    +
    +    # anonymize the text, replace the pii entities with the labels
    +    anonymized_text = _anonymize(text, results, entities_operator_map, is_full_text)
    +
    +    return anonymized_text, results
    +
    +
    +def _get_single_html(
    +    text: str, results: List[pa.RecognizerResult], is_full_html: bool = True
    +):
    +    """
    +    Generate the html for a single txt file.
    +
    +    :param text:         The text for analysis.
    +    :param results:      The list of Presidio RecognizerResult constructed from analysis.
    +    :param is_full_html: Whether generate full html or not.
    +
    +    :returns: The html string for a single txt file.
    +    """
    +    # convert the results to tokens to generate the html
    +    tokens = _annotate(text, results, is_full_html)
    +    html = at_util.get_annotated_html(*tokens)
    +
    +    # avoid the error during rendering of the \n in the html
    +    backslash_char = "\\"
    +
    +    html_str = f"

    {html.replace('{backslash_char}n', '
    ')}

    " + + return html_str + + +def _get_single_json(results: List[pa.RecognizerResult], is_full_report: bool = True): + """ + Generate the json for a single txt file. + + :param results: The list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full json or not. + + :returns: The json string for a single txt file. + """ + # generate the stats report if needed + if not is_full_report: + stats = [] + # add the simplify stats logic here + for item in results: + item.analysis_explanation = None + stats.append(item) + else: + stats = results + + return stats + + +def _get_all_html( + txt_content: dict, + res_dict: dict, + is_full_html: bool = True, +): + """ + Generate the html for all txt files. + + :param txt_content: The dictionary of txt file name and content. + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_html: Whether generate full html or not. + + :returns: The html string for all txt files. + + """ + # These are placeholder for the html string + html_index = "Highlighted Pii Entities

    Highlighted Pii Entities

      " + html_content = "" + for txt_file, results in res_dict.items(): + txt = txt_content[txt_file] + html_index += f"
    • {txt_file}
    • " + html_content += f"
    • {txt_file}

      {_get_single_html(txt, results, is_full_html)}

    • " + html_index += "
    " + html_res = f"{html_index}{html_content}" + + return html_res + + +def _get_all_rpt(res_dict: dict, is_full_report: bool = True): + """ + Generate the stats report for all txt files. + + :param res_dict: The dictionary of txt file name and the list of Presidio RecognizerResult constructed from analysis. + :param is_full_report: Whether generate full report or not. + + :returns: The stats report for all txt files. + """ + # These are placeholder for the json report + stats_dict = {} + for txt_file, results in res_dict.items(): + new_stats = [] + for item in _get_single_json(results, is_full_report): + if is_full_report: + item.analysis_explanation = item.analysis_explanation.to_dict() + new_stats.append(item.to_dict()) + else: + tmp_dict = item.to_dict() + tmp_dict.pop("analysis_explanation") + tmp_dict.pop("recognition_metadata") + new_stats.append(tmp_dict) + stats_dict[txt_file] = new_stats + return stats_dict + + +def recognize_pii( + context: mlrun.MLClientCtx, + input_path: Union[str, pathlib.Path], + html_key: str, + score_threshold: float, + output_directory: str = None, + entities: List[ + str + ] = None, # List of entities to recognize, default is recognizing all + entity_operator_map: dict = None, + model: str = None, + generate_json: bool = True, + generate_html: bool = True, + is_full_text: bool = True, + is_full_html: bool = True, + is_full_report: bool = True, +) -> Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, dict]]: + """ + Walk through the input path, recognize PII in text and store the anonymized text in the output path. + Generate the html with different colors for each entity, json report of the explanation. + + :param context: The MLRun context. this is needed for log the artifacts. + :param input_path: The input path of the text files needs to be analyzed. + :param html_key: The html key for the artifact. + :param score_threshold: The score threshold to mark the recognition as trusted. + :param output_directory: The output directory path to store the anonymized text. + :param entities: The list of entities to recognize. + :param entity_operator_map: The map of entity to operator (mask, redact, replace, keep, hash, and its params) + :param model: The model to use. Can be "spacy", "flair", "pattern" or "whole". + :param generate_json: Whether to generate the json report of the explanation. + :param generate_html: Whether to generate the html report of the explanation. + :param is_full_text: Whether to return the full text or only the masked text. + :param is_full_html: Whether to return the full html or just the annotated text + :param is_full_report: Whether to return the full report or just the score and start, end index + + :returns: A tuple of: + + * Path to the output directory + * The json report of the explanation (if generate_json is True) + * A dictionary of errors files that were not processed + + """ + + # Set output directory + if output_directory is None: + output_directory = tempfile.mkdtemp() + + # Create the output directory: + output_directory = pathlib.Path(output_directory) + if not output_directory.exists(): + output_directory.mkdir(parents=True, exist_ok=True) + + txt_files_directory = pathlib.Path(input_path) + successes = [] + errors = {} + + res_dict = {} + txt_content = {} + # Load the model: + analyzer = _get_analyzer_engine(model, entities) + logger.info("Model loaded") + # Go over the text files in the input path, analyze and anonymize them: + for txt_file in tqdm( + list(txt_files_directory.glob("*.txt")), + desc="Processing files", + unit="file", + ): + try: + # Load the str from the text file + text = txt_file.read_text() + txt_content[str(txt_file)] = text + # Process the text to recoginze the pii entities in it + anonymized_text, results = _process( + text=text, + model=analyzer, + entities=entities, + entities_operator_map=entity_operator_map, + score_threshold=score_threshold, + is_full_text=is_full_text, + ) + res_dict[str(txt_file)] = results + # Store the anonymized text in the output path + output_file = output_directory / f"{txt_file.stem}.txt" + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + f.write(anonymized_text) + successes.append([txt_file.name, output_file.name]) + except Exception as e: + errors[str(txt_file)] = str(e) + logger.error(f"Error processing {txt_file}: {e}") + + successes = pd.DataFrame( + successes, + columns=["original_file", "anonymized_file"], + ) + + if generate_html: + # Generate the html report + html_res = _get_all_html(txt_content, res_dict, is_full_html) + # Store the html report in the context + arti_html = mlrun.artifacts.Artifact(body=html_res, format="html", key=html_key) + context.log_artifact(arti_html) + if generate_json: + # Generate the json report + json_res = _get_all_rpt(res_dict, is_full_report) + return str(output_directory), successes, errors, json_res + return str(output_directory), successes, errors + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pii_recognizer/latest/src/function.yaml b/functions/master/pii_recognizer/latest/src/function.yaml index 069fa1ff..e7d6c124 100644 --- a/functions/master/pii_recognizer/latest/src/function.yaml +++ b/functions/master/pii_recognizer/latest/src/function.yaml @@ -1,38 +1,14 @@ -kind: job -metadata: - name: pii-recognizer - tag: '' - hash: 818930645d33704e9cada919769ee9d93cbb9434 - project: '' - labels: - author: pgw - categories: - - machine-learning - - data-preparation - - NLP +verbose: false spec: - command: '' - args: [] - image: '' - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo= - base_image: mlrun/mlrun - commands: [] - code_origin: '' - origin_filename: '' - requirements: - - nltk - - pandas - - presidio-anonymizer - - presidio-analyzer - - torch - - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653 - - st-annotated-text - - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl + default_handler: recognize_pii entry_points: analyze: name: analyze - doc: Analyze text and return the results. + outputs: + - doc: The list of Presidio RecognizerResult constructed from the recognized + Flair detections. + type: List[pa.RecognizerResult] + has_kwargs: false parameters: - name: self - name: text @@ -45,20 +21,16 @@ spec: type: pa.nlp_engine.NlpArtifacts doc: Not used by this recognizer but needed for the interface. default: null - outputs: - - doc: The list of Presidio RecognizerResult constructed from the recognized - Flair detections. - type: List[pa.RecognizerResult] lineno: 381 + doc: Analyze text and return the results. has_varargs: false - has_kwargs: false recognize_pii: name: recognize_pii - doc: 'Walk through the input path, recognize PII in text and store the anonymized - text in the output path. - - Generate the html with different colors for each entity, json report of the - explanation.' + outputs: + - doc: 'A tuple of:' + type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, + dict]] + has_kwargs: false parameters: - name: context type: MLClientCtx @@ -109,21 +81,35 @@ spec: type: bool doc: Whether to return the full report or just the score and start, end index default: true - outputs: - - doc: 'A tuple of:' - type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame, - dict]] lineno: 845 + doc: 'Walk through the input path, recognize PII in text and store the anonymized + text in the output path. + + Generate the html with different colors for each entity, json report of the + explanation.' has_varargs: false - has_kwargs: false + build: + base_image: mlrun/mlrun + requirements: + - nltk + - pandas + - presidio-anonymizer + - presidio-analyzer + - torch + - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653 + - st-annotated-text + - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo= + code_origin: '' + origin_filename: '' description: This function is used to recognize PII in a directory of text files - default_handler: recognize_pii + image: '' + command: '' disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false +kind: job +metadata: + name: pii-recognizer + tag: '' + categories: + - data-preparation + - NLP diff --git a/functions/master/pii_recognizer/latest/src/item.yaml b/functions/master/pii_recognizer/latest/src/item.yaml index 41ead33b..8f3185b4 100644 --- a/functions/master/pii_recognizer/latest/src/item.yaml +++ b/functions/master/pii_recognizer/latest/src/item.yaml @@ -1,6 +1,5 @@ apiVersion: v1 categories: - - machine-learning - data-preparation - NLP description: This function is used to recognize PII in a directory of text files @@ -13,7 +12,7 @@ labels: author: pgw maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.0 +mlrunVersion: 1.7.0 name: pii-recognizer platformVersion: 3.5.3 spec: @@ -31,5 +30,5 @@ spec: - st-annotated-text - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl url: '' -version: 0.3.0 +version: 0.4.0 test_valid: False diff --git a/functions/master/pii_recognizer/latest/static/documentation.html b/functions/master/pii_recognizer/latest/static/documentation.html index fdad808c..fbf1f167 100644 --- a/functions/master/pii_recognizer/latest/static/documentation.html +++ b/functions/master/pii_recognizer/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pii_recognizer/latest/static/example.html b/functions/master/pii_recognizer/latest/static/example.html index d5a156b8..e403a056 100644 --- a/functions/master/pii_recognizer/latest/static/example.html +++ b/functions/master/pii_recognizer/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pii_recognizer/latest/static/function.html b/functions/master/pii_recognizer/latest/static/function.html index fe3339fa..92eba7ca 100644 --- a/functions/master/pii_recognizer/latest/static/function.html +++ b/functions/master/pii_recognizer/latest/static/function.html @@ -28,41 +28,17 @@
             
    -kind: job
    -metadata:
    -  name: pii-recognizer
    -  tag: ''
    -  hash: 818930645d33704e9cada919769ee9d93cbb9434
    -  project: ''
    -  labels:
    -    author: pgw
    -  categories:
    -  - machine-learning
    -  - data-preparation
    -  - NLP
    +verbose: false
     spec:
    -  command: ''
    -  args: []
    -  image: ''
    -  build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo=
    -    base_image: mlrun/mlrun
    -    commands: []
    -    code_origin: ''
    -    origin_filename: ''
    -    requirements:
    -    - nltk
    -    - pandas
    -    - presidio-anonymizer
    -    - presidio-analyzer
    -    - torch
    -    - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653
    -    - st-annotated-text
    -    - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl
    +  default_handler: recognize_pii
       entry_points:
         analyze:
           name: analyze
    -      doc: Analyze text and return the results.
    +      outputs:
    +      - doc: The list of Presidio RecognizerResult constructed from the recognized
    +          Flair detections.
    +        type: List[pa.RecognizerResult]
    +      has_kwargs: false
           parameters:
           - name: self
           - name: text
    @@ -75,20 +51,16 @@
             type: pa.nlp_engine.NlpArtifacts
             doc: Not used by this recognizer but needed for the interface.
             default: null
    -      outputs:
    -      - doc: The list of Presidio RecognizerResult constructed from the recognized
    -          Flair detections.
    -        type: List[pa.RecognizerResult]
           lineno: 381
    +      doc: Analyze text and return the results.
           has_varargs: false
    -      has_kwargs: false
         recognize_pii:
           name: recognize_pii
    -      doc: 'Walk through the input path, recognize PII in text and store the anonymized
    -        text in the output path.
    -
    -        Generate the html with different colors for each entity, json report of the
    -        explanation.'
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame,
    +          dict]]
    +      has_kwargs: false
           parameters:
           - name: context
             type: MLClientCtx
    @@ -139,24 +111,38 @@
             type: bool
             doc: Whether to return the full report or just the score and start, end index
             default: true
    -      outputs:
    -      - doc: 'A tuple of:'
    -        type: Union[Tuple[str, pd.DataFrame, dict, dict], Tuple[str, pd.DataFrame,
    -          dict]]
           lineno: 845
    +      doc: 'Walk through the input path, recognize PII in text and store the anonymized
    +        text in the output path.
    +
    +        Generate the html with different colors for each entity, json report of the
    +        explanation.'
           has_varargs: false
    -      has_kwargs: false
    +  build:
    +    base_image: mlrun/mlrun
    +    requirements:
    +    - nltk
    +    - pandas
    +    - presidio-anonymizer
    +    - presidio-analyzer
    +    - torch
    +    - flair@git+https://github.com/flairNLP/flair.git@d4ed67bf663e4066517f00397412510d90043653
    +    - st-annotated-text
    +    - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9zCmltcG9ydCBwYXRobGliCmltcG9ydCB0ZW1wZmlsZQppbXBvcnQgd2FybmluZ3MKZnJvbSB0eXBpbmcgaW1wb3J0IExpc3QsIFNldCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgYW5ub3RhdGVkX3RleHQudXRpbCBhcyBhdF91dGlsCmltcG9ydCBtbHJ1bgppbXBvcnQgbmx0awppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBwcmVzaWRpb19hbmFseXplciBhcyBwYQppbXBvcnQgcHJlc2lkaW9fYW5vbnltaXplciBhcyBwcmVfYW5veW1pemVyCmZyb20gcHJlc2lkaW9fYW5vbnltaXplci5lbnRpdGllcyBpbXBvcnQgT3BlcmF0b3JDb25maWcKZnJvbSB0cWRtIGltcG9ydCB0cWRtCgp0cnk6CiAgICBpbXBvcnQgZmxhaXIgYXMgZmwKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBwcmludCgiRmxhaXIgaXMgbm90IGluc3RhbGxlZCIpCgojIFRoZXJlIGlzIGEgY29uZmxpY3QgYmV0d2VlbiBSdXN0LWJhc2VkIHRva2VuaXplcnMnIHBhcmFsbGVsIHByb2Nlc3NpbmcKIyBhbmQgUHl0aG9uJ3MgZm9yayBvcGVyYXRpb25zIGR1cmluZyBtdWx0aXByb2Nlc3NpbmcuIFRvIGF2b2lkIHRoaXMsIHdlIG5lZWQKIyB0aGUgZm9sbG93aW5nIHR3byBsaW5lcwoKb3MuZW52aXJvblsiVE9LRU5JWkVSU19QQVJBTExFTElTTSJdID0gImZhbHNlIgp3YXJuaW5ncy5maWx0ZXJ3YXJuaW5ncygiaWdub3JlIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJwaWktcmVjb2duaXplciIpCgoKIyBBZGQgdGhlIGNvbnN0YW50IGNsYXNzZXMgb2YgTW9kZWxzIGFuZCBFbnRpdGllcyB0byBnb3Zlcm4gdGhlIHdob2xlIHBhY2thZ2UKY2xhc3MgTW9kZWxzOgogICAgV0hPTEUgPSAid2hvbGUiCiAgICBQQVRURVJOID0gInBhdHRlcm4iCiAgICBTUEFDWSA9ICJzcGFjeSIKICAgIEZMQUlSID0gImZsYWlyIgoKCmNsYXNzIEVudGl0aWVzOgogICAgQ1JFRElUX0NBUkQgPSAiQ1JFRElUX0NBUkQiCiAgICBTU04gPSAiU1NOIgogICAgUEhPTkUgPSAiUEhPTkUiCiAgICBFTUFJTCA9ICJFTUFJTCIKICAgIExPQ0FUSU9OID0gIkxPQ0FUSU9OIgogICAgUEVSU09OID0gIlBFUlNPTiIKICAgIE5SUCA9ICJOUlAiCiAgICBPUkdBTklaQVRJT04gPSAiT1JHQU5JWkFUSU9OIgogICAgREFURV9USU1FID0gIkRBVEVfVElNRSIKICAgIEdQRSA9ICgiR1BFIiwpCiAgICBNQUNfQUREUkVTUyA9ICJNQUNfQUREUkVTUyIKICAgIFVTX0JBTktfTlVNQkVSID0gIlVTX0JBTktfTlVNQkVSIgogICAgSU1FSSA9ICJJTUVJIgogICAgVElUTEUgPSAiVElUTEUiCiAgICBMSUNFTlNFX1BMQVRFID0gIkxJQ0VOU0VfUExBVEUiCiAgICBVU19QQVNTUE9SVCA9ICJVU19QQVNTUE9SVCIKICAgIENVUlJFTkNZID0gIkNVUlJFTkNZIgogICAgUk9VVElOR19OVU1CRVIgPSAiUk9VVElOR19OVU1CRVIiCiAgICBVU19JVElOID0gIlVTX0lUSU4iCiAgICBVU19CQU5LX05VTUJFUiA9ICJVU19CQU5LX05VTUJFUiIKICAgIFVTX0RSSVZFUl9MSUNFTlNFID0gIlVTX0RSSVZFUl9MSUNFTlNFIgogICAgQUdFID0gIkFHRSIKICAgIFBBU1NXT1JEID0gIlBBU1NXT1JEIgogICAgU1dJRlRfQ09ERSA9ICJTV0lGVF9DT0RFIgoKCmNsYXNzIFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeToKICAgICIiIgogICAgRmFjdG9yeSBmb3IgY3JlYXRpbmcgcGF0dGVybiByZWNvZ25pemVycywgaXQgY2FuIGJlIGV4dGVuZGVkIGluIHRoZSBmdXR1cmUgdG8KICAgIGFkZCBtb3JlIHJlZ2V4IHBhdHRlcm4gZm9yIGRpZmZlcmVudCBlbnRpdGllcy4gRm9yIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIgdG8gd29yaywKICAgIHdlIG5lZWQgY29uc3RydWN0IGEgbGlzdCBvZiByZWdleCBwYXR0ZXJucyBmb3IgZWFjaCBlbnRpdHkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkNSRURJVF9DQVJEIjogW3BhLlBhdHRlcm4oIkNSRURJVF9DQVJEIiwgciJcYig/OlxkWyAtXSo/KXsxMywxNn1cYiIsIDAuNSldLAogICAgICAgICJTU04iOiBbcGEuUGF0dGVybigiU1NOIiwgciJcYlxkezN9LT9cZHsyfS0/XGR7NH1cYiIsIDAuNSldLAogICAgICAgICJQSE9ORSI6IFtwYS5QYXR0ZXJuKCJQSE9ORSIsIHIiXCg/XGR7M31cKT9bLS5cc10/XGR7M31bLS5cc10/XGR7NH0iLCAwLjUpXSwKICAgICAgICAiRU1BSUwiOiBbcGEuUGF0dGVybigiRU1BSUwiLCByIlxTK0BcUysiLCAwLjUpXSwKICAgIH0KCiAgICAjIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybiByZWNvZ25pemVycwogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoY2xzKToKICAgICAgICAiIiIKICAgICAgICBGb3IgZWFjaCBlbnRpdHksIGNyZWF0ZSBhIGxpc3Qgb2YgcGF0dGVybnMgdG8gcmVjb2duaXplIGl0CgogICAgICAgIDpwYXJhbSBjbHM6IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSBjbGFzcwoKICAgICAgICA6cmV0dXJuczogTGlzdCBvZiBwYXR0ZXJuIHJlY29nbml6ZXJzCiAgICAgICAgIiIiCgogICAgICAgICMgRW50aXRpZXMgdG8gcmVjb2duaXplIGFuZCB0aGVpciByZWdleCBwYXR0ZXJucwoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBwYS5QYXR0ZXJuUmVjb2duaXplcihzdXBwb3J0ZWRfZW50aXR5PWVudGl0eSwgcGF0dGVybnM9cGF0dGVybikKICAgICAgICAgICAgZm9yIGVudGl0eSwgcGF0dGVybiBpbiBjbHMuUkVDT0dOSVpBQkxFX0VOVElUSUVTLml0ZW1zKCkKICAgICAgICBdCgoKY2xhc3MgQ3VzdG9tU3BhY3lSZWNvZ25pemVyKHBhLkxvY2FsUmVjb2duaXplcik6CiAgICAiIiIKICAgIEN1c3RvbSBTcGFjeSBSZWNvZ25pemVyIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIgdHJhaW5lZCBvbiBQcml2eSBkYXRhLgogICAgVGhlIHByaXZ5IGRhdGEgaXMgZ2VuZXJhdGVkIHVzaW5nIHRoaXMgaHR0cHM6Ly9naXRodWIuY29tL3BpeGllLWlvL3BpeGllL3RyZWUvbWFpbi9zcmMvZGF0YWdlbi9waWkvcHJpdnkKICAgIEl0IGNhbiBiZSB1c2VkIHRvIHJlY29nbml6ZSBjdXN0b20gZW50aXRpZXMsIFNpbmNlIHdlIHdhbnQgdG8gdXNlIFByZXNpZGlvJ3MgUmVnaXN0cmllcyB0byBnZW5lcmF0ZSBBbmFseXplckVuZ2luZSwKICAgIGl0IGluaGVyaXRzIGZyb20gUHJlc2lkaW8gQW5hbHl6ZXIncyBMb2NhbFJlY29nbml6ZXIgY2xhc3MuCiAgICAiIiIKCiAgICAjIEVudGl0aWVzIHRvIHJlY29nbml6ZQoKICAgIFJFQ09HTklaQUJMRV9FTlRJVElFUyA9IHsKICAgICAgICAiTE9DQVRJT04iLAogICAgICAgICJQRVJTT04iLAogICAgICAgICJOUlAiLAogICAgICAgICJPUkdBTklaQVRJT04iLAogICAgICAgICJEQVRFX1RJTUUiLAogICAgfQoKICAgICMgRGVmYXVsdCBleHBsYW5hdGlvbiBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfRVhQTEFOQVRJT04gPSAoCiAgICAgICAgIklkZW50aWZpZWQgYXMge30gYnkgU3BhY3kncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKFByaXZ5LXRyYWluZWQpIgogICAgKQoKICAgICMgTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJPUkdBTklaQVRJT04ifSwgeyJPUkcifSksCiAgICAgICAgKHsiREFURV9USU1FIn0sIHsiREFURV9USU1FIn0pLAogICAgXQoKICAgICMgcHJldHJhaW5lZCBtb2RlbCBmb3IgdGhpcyByZWNvZ25pemVyCgogICAgX0RFRkFVTFRfTU9ERUxfTEFOR1VBR0VTID0gewogICAgICAgICJlbiI6ICJiZWtpL2VuX3NwYWN5X3BpaV9kaXN0aWxiZXJ0IiwKICAgIH0KCiAgICBfREVGQVVMVF9QUkVTSURJT19FUVVJVkFMRU5DRVMgPSB7CiAgICAgICAgIlBFUiI6ICJQRVJTT04iLAogICAgICAgICJMT0MiOiAiTE9DQVRJT04iLAogICAgICAgICJPUkciOiAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTlJPUCI6ICJOUlAiLAogICAgICAgICJEQVRFX1RJTUUiOiAiREFURV9USU1FIiwKICAgIH0KCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IHN0ciA9ICJlbiIsCiAgICAgICAgc3VwcG9ydGVkX2VudGl0aWVzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIGNoZWNrX2xhYmVsX2dyb3VwczogVHVwbGVbU2V0LCBTZXRdID0gTm9uZSwKICAgICAgICBjb250ZXh0OiBMaXN0W3N0cl0gPSBOb25lLAogICAgICAgIG5lcl9zdHJlbmd0aDogZmxvYXQgPSAxLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIFNwYWN5IFJlY29nbml6ZXIuCgogICAgICAgIDpwYXJhbSBzdXBwb3J0ZWRfbGFuZ3VhZ2U6IExhbmd1YWdlIHRvIHVzZSwgZGVmYXVsdCBpcyBFbmdsaXNoCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlIGZvciByZWNvZ25pdGlvbgogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjayBmb3IgdGhlIGVudGl0aWVzCiAgICAgICAgOnBhcmFtIGNvbnRleHQ6ICAgICAgICAgICAgQ29udGV4dCB0byB1c2UgaWYgYW55CiAgICAgICAgOnBhcmFtIG5lcl9zdHJlbmd0aDogICAgICAgRGVmYXVsdCBjb25maWRlbmNlIGZvciBORVIgcHJlZGljdGlvbgoKICAgICAgICA6cmV0dXJuczogU3BhY3lSZWNvZ25pemVyIG9iamVjdAogICAgICAgICIiIgoKICAgICAgICAjIERlZmF1bHQgY29uZmlkZW5jZSBmb3IgTkVSIHByZWRpY3Rpb24KICAgICAgICBzZWxmLm5lcl9zdHJlbmd0aCA9IG5lcl9zdHJlbmd0aAoKICAgICAgICBzZWxmLmNoZWNrX2xhYmVsX2dyb3VwcyA9IGNoZWNrX2xhYmVsX2dyb3VwcyBvciBzZWxmLl9ERUZBVUxUX0NIRUNLX0xBQkVMX0dST1VQUwogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgKQoKICAgICMgZ2V0IHRoZSBwcmVzaWRpbyBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIGRlZiBfYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgc2VsZiwgb3JpZ2luYWxfc2NvcmU6IGZsb2F0LCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLkFuYWx5c2lzRXhwbGFuYXRpb246CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhpcyByZXN1bHQgd2FzIGRldGVjdGVkLgoKICAgICAgICA6cGFyYW0gb3JpZ2luYWxfc2NvcmU6IFNjb3JlIGdpdmVuIGJ5IHRoaXMgcmVjb2duaXplcgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogICAgRXhwbGFuYXRpb24gc3RyaW5nCgogICAgICAgIDpyZXR1cm5zOiBQcmVzaWRpbyBBbmFseXNpc0V4cGxhbmF0aW9uIG9iamVjdAogICAgICAgICIiIgogICAgICAgIGV4cGxhbmF0aW9uID0gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbigKICAgICAgICAgICAgcmVjb2duaXplcj1zZWxmLl9fY2xhc3NfXy5fX25hbWVfXywKICAgICAgICAgICAgb3JpZ2luYWxfc2NvcmU9b3JpZ2luYWxfc2NvcmUsCiAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb249ZXhwbGFuYXRpb24sCiAgICAgICAgKQogICAgICAgIHJldHVybiBleHBsYW5hdGlvbgoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZShzZWxmLCB0ZXh0OiBzdHIsIGVudGl0aWVzOiBMaXN0W3N0cl0sIG5scF9hcnRpZmFjdHM9Tm9uZSk6ICAjIG5vcWEgRDEwMgogICAgICAgICIiIgogICAgICAgIEFuYWx5emUgdGV4dCB1c2luZyBTcGFjeS4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRleHQgdG8gYW5hbHl6ZQogICAgICAgIDpwYXJhbSBlbnRpdGllczogICAgICBFbnRpdGllcyB0byBhbmFseXplCiAgICAgICAgOnBhcmFtIG5scF9hcnRpZmFjdHM6IE5MUCBhcnRpZmFjdHMgdG8gdXNlCgogICAgICAgIDpyZXR1cm5zOiBMaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgb2JqZWN0cwogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBbXQogICAgICAgIGlmIG5vdCBubHBfYXJ0aWZhY3RzOgogICAgICAgICAgICBsb2dnZXIud2FybmluZygiU2tpcHBpbmcgU3BhQ3ksIG5scCBhcnRpZmFjdHMgbm90IHByb3ZpZGVkLi4uIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICAgICAgbmVyX2VudGl0aWVzID0gbmxwX2FydGlmYWN0cy5lbnRpdGllcwoKICAgICAgICAjIHJlY29nbml6ZSB0aGUgc3VwcG9ydGVkIGVudGl0aWVzCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIGZvciBlbnQgaW4gbmVyX2VudGl0aWVzOgogICAgICAgICAgICAgICAgaWYgbm90IHNlbGYuX19jaGVja19sYWJlbChlbnRpdHksIGVudC5sYWJlbF8sIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzKToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgICMgc3RyaW5nIG9mIHRoZSBleHBsYW5hdGlvbiBzYXlpbmcgdGhlIGVudGl0eSBpcyByZWNvZ25pemVkIGJ5IHNwYWN5CiAgICAgICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uID0gc2VsZi5fREVGQVVMVF9FWFBMQU5BVElPTi5mb3JtYXQoZW50LmxhYmVsXykKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfc3BhY3lfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXJfc3RyZW5ndGgsIHRleHR1YWxfZXhwbGFuYXRpb24KICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIGNyZWF0ZSB0aGUgc3RhbmRhcmQgcmVzdWx0IHdpdGggdGhlIGVudGl0eSwgc3RhcnQsIGVuZCwgc2NvcmUsIGFuZCBleHBsYW5hdGlvbgogICAgICAgICAgICAgICAgc3BhY3lfcmVzdWx0ID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgICAgICAgICBlbnRpdHlfdHlwZT1lbnRpdHksCiAgICAgICAgICAgICAgICAgICAgc3RhcnQ9ZW50LnN0YXJ0X2NoYXIsCiAgICAgICAgICAgICAgICAgICAgZW5kPWVudC5lbmRfY2hhciwKICAgICAgICAgICAgICAgICAgICBzY29yZT1zZWxmLm5lcl9zdHJlbmd0aCwKICAgICAgICAgICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICByZWNvZ25pdGlvbl9tZXRhZGF0YT17CiAgICAgICAgICAgICAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQuUkVDT0dOSVpFUl9OQU1FX0tFWTogc2VsZi5uYW1lCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHNwYWN5X3Jlc3VsdCkKCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX19jaGVja19sYWJlbCgKICAgICAgICBlbnRpdHk6IHN0ciwgbGFiZWw6IHN0ciwgY2hlY2tfbGFiZWxfZ3JvdXBzOiBUdXBsZVtTZXQsIFNldF0KICAgICkgLT4gYm9vbDoKICAgICAgICAiIiIKICAgICAgICBDaGVjayBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLgoKICAgICAgICA6cGFyYW0gZW50aXR5OiAgICAgICAgICAgICBFbnRpdHkgdG8gY2hlY2sKICAgICAgICA6cGFyYW0gbGFiZWw6ICAgICAgICAgICAgICBMYWJlbCB0byBjaGVjawogICAgICAgIDpwYXJhbSBjaGVja19sYWJlbF9ncm91cHM6IExhYmVsIGdyb3VwcyB0byBjaGVjawoKICAgICAgICA6cmV0dXJuczogVHJ1ZSBpZiB0aGUgbGFiZWwgaXMgaW4gdGhlIGxhYmVsIGdyb3VwLCBGYWxzZSBvdGhlcndpc2UKICAgICAgICAiIiIKICAgICAgICByZXR1cm4gYW55KAogICAgICAgICAgICBlbnRpdHkgaW4gZWdycCBhbmQgbGFiZWwgaW4gbGdycCBmb3IgZWdycCwgbGdycCBpbiBjaGVja19sYWJlbF9ncm91cHMKICAgICAgICApCgoKIyBDbGFzcyB0byB1c2UgRmxhaXIgd2l0aCBQcmVzaWRpbyBhcyBhbiBleHRlcm5hbCByZWNvZ25pemVyLgpjbGFzcyBGbGFpclJlY29nbml6ZXIocGEuRW50aXR5UmVjb2duaXplcik6CiAgICAiIiIKICAgIFdyYXBwZXIgZm9yIGEgZmxhaXIgbW9kZWwsIGlmIG5lZWRlZCB0byBiZSB1c2VkIHdpdGhpbiBQcmVzaWRpbyBBbmFseXplci4KICAgIFRoaXMgaXMgdG8gbWFrZSBzdXJlIHRoZSByZWNvZ25pemVyIGNhbiBiZSByZWdpc3RlcmVkIHdpdGggUHJlc2lkaW8gcmVnaXN0cnkuCiAgICAiIiIKCiAgICBSRUNPR05JWkFCTEVfRU5USVRJRVMgPSB7CiAgICAgICAgIkxPQ0FUSU9OIiwKICAgICAgICAiUEVSU09OIiwKICAgICAgICAiTlJQIiwKICAgICAgICAiR1BFIiwKICAgICAgICAiT1JHQU5JWkFUSU9OIiwKICAgICAgICAiTUFDX0FERFJFU1MiLAogICAgICAgICJVU19CQU5LX05VTUJFUiIsCiAgICAgICAgIklNRUkiLAogICAgICAgICJUSVRMRSIsCiAgICAgICAgIkxJQ0VOU0VfUExBVEUiLAogICAgICAgICJVU19QQVNTUE9SVCIsCiAgICAgICAgIkNVUlJFTkNZIiwKICAgICAgICAiUk9VVElOR19OVU1CRVIiLAogICAgICAgICJVU19JVElOIiwKICAgICAgICAiVVNfQkFOS19OVU1CRVIiLAogICAgICAgICJVU19EUklWRVJfTElDRU5TRSIsCiAgICAgICAgIkFHRSIsCiAgICAgICAgIlBBU1NXT1JEIiwKICAgICAgICAiU1dJRlRfQ09ERSIsCiAgICB9CgogICAgIyBUaGlzIGlzIHVzZWQgdG8gY29uc3RydWN0IHRoZSBleHBsYW5hdGlvbiBmb3IgdGhlIHJlc3VsdAoKICAgIF9ERUZBVUxUX0VYUExBTkFUSU9OID0gIklkZW50aWZpZWQgYXMge30gYnkgRmxhaXIncyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24iCgogICAgX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTID0gWwogICAgICAgICh7IkxPQ0FUSU9OIn0sIHsiTE9DIiwgIkxPQ0FUSU9OIiwgIlNUUkVFVF9BRERSRVNTIiwgIkNPT1JESU5BVEUifSksCiAgICAgICAgKHsiUEVSU09OIn0sIHsiUEVSIiwgIlBFUlNPTiJ9KSwKICAgICAgICAoeyJOUlAifSwgeyJOT1JQIiwgIk5SUCJ9KSwKICAgICAgICAoeyJHUEUifSwgeyJHUEUifSksCiAgICAgICAgKHsiT1JHQU5JWkFUSU9OIn0sIHsiT1JHIn0pLAogICAgICAgICh7Ik1BQ19BRERSRVNTIn0sIHsiTUFDX0FERFJFU1MifSksCiAgICAgICAgKHsiVVNfQkFOS19OVU1CRVIifSwgeyJVU19CQU5LX05VTUJFUiJ9KSwKICAgICAgICAoeyJJTUVJIn0sIHsiSU1FSSJ9KSwKICAgICAgICAoeyJUSVRMRSJ9LCB7IlRJVExFIn0pLAogICAgICAgICh7IkxJQ0VOU0VfUExBVEUifSwgeyJMSUNFTlNFX1BMQVRFIn0pLAogICAgICAgICh7IlVTX1BBU1NQT1JUIn0sIHsiVVNfUEFTU1BPUlQifSksCiAgICAgICAgKHsiQ1VSUkVOQ1kifSwgeyJDVVJSRU5DWSJ9KSwKICAgICAgICAoeyJST1VUSU5HX05VTUJFUiJ9LCB7IlJPVVRJTkdfTlVNQkVSIn0pLAogICAgICAgICh7IkFHRSJ9LCB7IkFHRSJ9KSwKICAgICAgICAoeyJDVVJSRU5DWSJ9LCB7IkNVUlJFTkNZIn0pLAogICAgICAgICh7IlNXSUZUX0NPREUifSwgeyJTV0lGVF9DT0RFIn0pLAogICAgICAgICh7IlVTX0lUSU4ifSwgeyJVU19JVElOIn0pLAogICAgICAgICh7IlVTX0JBTktfTlVNQkVSIn0sIHsiVVNfQkFOS19OVU1CRVIifSksCiAgICAgICAgKHsiVVNfRFJJVkVSX0xJQ0VOU0UifSwgeyJVU19EUklWRVJfTElDRU5TRSJ9KSwKICAgIF0KCiAgICBfREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMgPSB7CiAgICAgICAgImVuIjogImJla2kvZmxhaXItcGlpLWRpc3RpbGJlcnQiLAogICAgfQoKICAgIF9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUyA9IHsKICAgICAgICAiUEVSIjogIlBFUlNPTiIsCiAgICAgICAgIkxPQyI6ICJMT0NBVElPTiIsCiAgICAgICAgIk9SRyI6ICJPUkdBTklaQVRJT04iLAogICAgICAgICJOUk9QIjogIk5SUCIsCiAgICAgICAgIlVSTCI6ICJVUkwiLAogICAgICAgICJVU19JVElOIjogIlVTX0lUSU4iLAogICAgICAgICJVU19QQVNTUE9SVCI6ICJVU19QQVNTUE9SVCIsCiAgICAgICAgIklCQU5fQ09ERSI6ICJJQkFOX0NPREUiLAogICAgICAgICJJUF9BRERSRVNTIjogIklQX0FERFJFU1MiLAogICAgICAgICJFTUFJTF9BRERSRVNTIjogIkVNQUlMIiwKICAgICAgICAiVVNfRFJJVkVSX0xJQ0VOU0UiOiAiVVNfRFJJVkVSX0xJQ0VOU0UiLAogICAgICAgICJVU19CQU5LX05VTUJFUiI6ICJVU19CQU5LX05VTUJFUiIsCiAgICB9CgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsCiAgICAgICAgc3VwcG9ydGVkX2xhbmd1YWdlOiBzdHIgPSAiZW4iLAogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllczogTGlzdFtzdHJdID0gTm9uZSwKICAgICAgICBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XSA9IE5vbmUsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIEZsYWlyUmVjb2duaXplci4KCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9sYW5ndWFnZTogTGFuZ3VhZ2UgdG8gdXNlCiAgICAgICAgOnBhcmFtIHN1cHBvcnRlZF9lbnRpdGllczogRW50aXRpZXMgdG8gdXNlCiAgICAgICAgOnBhcmFtIGNoZWNrX2xhYmVsX2dyb3VwczogTGFiZWwgZ3JvdXBzIHRvIGNoZWNrCgogICAgICAgIDpyZXR1cm5zOiBGbGFpclJlY29nbml6ZXIgb2JqZWN0CgogICAgICAgICIiIgogICAgICAgIHNlbGYuY2hlY2tfbGFiZWxfZ3JvdXBzID0gY2hlY2tfbGFiZWxfZ3JvdXBzIG9yIHNlbGYuX0RFRkFVTFRfQ0hFQ0tfTEFCRUxfR1JPVVBTCgogICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IHN1cHBvcnRlZF9lbnRpdGllcyBvciBzZWxmLlJFQ09HTklaQUJMRV9FTlRJVElFUwogICAgICAgIHNlbGYubW9kZWwgPSBmbC5tb2RlbHMuU2VxdWVuY2VUYWdnZXIubG9hZCgKICAgICAgICAgICAgc2VsZi5fREVGQVVMVF9NT0RFTF9MQU5HVUFHRVMuZ2V0KHN1cHBvcnRlZF9sYW5ndWFnZSkKICAgICAgICApCgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIHN1cHBvcnRlZF9lbnRpdGllcz1zdXBwb3J0ZWRfZW50aXRpZXMsCiAgICAgICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZT1zdXBwb3J0ZWRfbGFuZ3VhZ2UsCiAgICAgICAgICAgIG5hbWU9IkZsYWlyIEFuYWx5dGljcyIsCiAgICAgICAgKQoKICAgICMgbWFpbiBtZXRob2QgZm9yIHRoZSByZWNvZ25pemVyCiAgICBkZWYgYW5hbHl6ZSgKICAgICAgICBzZWxmLAogICAgICAgIHRleHQ6IHN0ciwKICAgICAgICBlbnRpdGllczogTGlzdFtzdHJdLAogICAgICAgIG5scF9hcnRpZmFjdHM6IHBhLm5scF9lbmdpbmUuTmxwQXJ0aWZhY3RzID0gTm9uZSwKICAgICkgLT4gTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XToKICAgICAgICAiIiIKICAgICAgICBBbmFseXplIHRleHQgYW5kIHJldHVybiB0aGUgcmVzdWx0cy4KCiAgICAgICAgOnBhcmFtIHRleHQ6ICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgICAgICA6cGFyYW0gZW50aXRpZXM6ICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgICAgIDpwYXJhbSBubHBfYXJ0aWZhY3RzOiBOb3QgdXNlZCBieSB0aGlzIHJlY29nbml6ZXIgYnV0IG5lZWRlZCBmb3IgdGhlIGludGVyZmFjZS4KCiAgICAgICAgOnJldHVybnM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSB0aGUgcmVjb2duaXplZCBGbGFpciBkZXRlY3Rpb25zLgogICAgICAgICIiIgoKICAgICAgICByZXN1bHRzID0gW10KCiAgICAgICAgc2VudGVuY2VzID0gZmwuZGF0YS5TZW50ZW5jZSh0ZXh0KQogICAgICAgIHNlbGYubW9kZWwucHJlZGljdChzZW50ZW5jZXMpCgogICAgICAgICMgSWYgdGhlcmUgYXJlIG5vIHNwZWNpZmljIGxpc3Qgb2YgZW50aXRpZXMsIHdlIHdpbGwgbG9vayBmb3IgYWxsIG9mIGl0LgogICAgICAgIGlmIG5vdCBlbnRpdGllczoKICAgICAgICAgICAgZW50aXRpZXMgPSBzZWxmLnN1cHBvcnRlZF9lbnRpdGllcwoKICAgICAgICAjIEdvIG92ZXIgdGhlIGVudGl0aWVzIGFuZCBjaGVjayBpZiB0aGV5IGFyZSBpbiB0aGUgc3VwcG9ydGVkIGVudGl0aWVzIGxpc3QuCiAgICAgICAgZm9yIGVudGl0eSBpbiBlbnRpdGllczoKICAgICAgICAgICAgaWYgZW50aXR5IG5vdCBpbiBzZWxmLnN1cHBvcnRlZF9lbnRpdGllczoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAjIEdvIG92ZXIgdGhlIHNlbnRlbmNlcyBhbmQgY2hlY2sgaWYgdGhlIGVudGl0eSBpcyBpbiB0aGUgc2VudGVuY2UuCiAgICAgICAgICAgIGZvciBlbnQgaW4gc2VudGVuY2VzLmdldF9zcGFucygibmVyIik6CiAgICAgICAgICAgICAgICBpZiBub3Qgc2VsZi5fX2NoZWNrX2xhYmVsKAogICAgICAgICAgICAgICAgICAgIGVudGl0eSwgZW50LmxhYmVsc1swXS52YWx1ZSwgc2VsZi5jaGVja19sYWJlbF9ncm91cHMKICAgICAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICAjIElmIHRoZSBlbnRpdHkgaXMgaW4gdGhlIHNlbnRlbmNlLCB3ZSB3aWxsIGFkZCBpdCB0byB0aGUgcmVzdWx0cy4KICAgICAgICAgICAgICAgIHRleHR1YWxfZXhwbGFuYXRpb24gPSBzZWxmLl9ERUZBVUxUX0VYUExBTkFUSU9OLmZvcm1hdCgKICAgICAgICAgICAgICAgICAgICBlbnQubGFiZWxzWzBdLnZhbHVlCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgZXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICAgICAgICAgIGV4cGxhbmF0aW9uID0gc2VsZi5fYnVpbGRfZmxhaXJfZXhwbGFuYXRpb24oCiAgICAgICAgICAgICAgICAgICAgcm91bmQoZW50LnNjb3JlLCAyKSwgdGV4dHVhbF9leHBsYW5hdGlvbgogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIGZsYWlyX3Jlc3VsdCA9IHNlbGYuX2NvbnZlcnRfdG9fcmVjb2duaXplcl9yZXN1bHQoZW50LCBleHBsYW5hdGlvbikKCiAgICAgICAgICAgICAgICByZXN1bHRzLmFwcGVuZChmbGFpcl9yZXN1bHQpCgogICAgICAgIHJldHVybiByZXN1bHRzCgogICAgZGVmIF9jb252ZXJ0X3RvX3JlY29nbml6ZXJfcmVzdWx0KAogICAgICAgIHNlbGYsIGVudGl0eTogZmwuZGF0YS5TcGFuLCBleHBsYW5hdGlvbjogc3RyCiAgICApIC0+IHBhLlJlY29nbml6ZXJSZXN1bHQ6CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCBGbGFpciByZXN1bHQgdG8gUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdC4KCiAgICAgICAgOnBhcmFtIGVudGl0eTogICAgICBGbGFpciBlbnRpdHkgb2YgU3BhbgogICAgICAgIDpwYXJhbSBleHBsYW5hdGlvbjogUHJlc2lkaW8gQW5hbHlzaXNFeHBsYW5hdGlvbgoKICAgICAgICA6cmV0dXJuczogUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdAogICAgICAgICIiIgoKICAgICAgICAjIENvbnZlcnQgdGhlIGVudGl0eSB0eXBlIHRvIFByZXNpZGlvIGVudGl0eSB0eXBlCiAgICAgICAgZW50aXR5X3R5cGUgPSBzZWxmLl9ERUZBVUxUX1BSRVNJRElPX0VRVUlWQUxFTkNFUy5nZXQoZW50aXR5LnRhZywgZW50aXR5LnRhZykKCiAgICAgICAgIyBDb252ZXJ0IHRoZSBzY29yZSB0byBQcmVzaWRpbyBzY29yZQogICAgICAgIGZsYWlyX3Njb3JlID0gcm91bmQoZW50aXR5LnNjb3JlLCAyKQoKICAgICAgICAjIENyZWF0ZSB0aGUgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBmcm9tIHRoZSBGbGFpciBlbnRpdHkKICAgICAgICBmbGFpcl9yZXN1bHRzID0gcGEuUmVjb2duaXplclJlc3VsdCgKICAgICAgICAgICAgZW50aXR5X3R5cGU9ZW50aXR5X3R5cGUsCiAgICAgICAgICAgIHN0YXJ0PWVudGl0eS5zdGFydF9wb3NpdGlvbiwKICAgICAgICAgICAgZW5kPWVudGl0eS5lbmRfcG9zaXRpb24sCiAgICAgICAgICAgIHNjb3JlPWZsYWlyX3Njb3JlLAogICAgICAgICAgICBhbmFseXNpc19leHBsYW5hdGlvbj1leHBsYW5hdGlvbiwKICAgICAgICApCgogICAgICAgIHJldHVybiBmbGFpcl9yZXN1bHRzCgogICAgZGVmIF9idWlsZF9mbGFpcl9leHBsYW5hdGlvbigKICAgICAgICBzZWxmLCBvcmlnaW5hbF9zY29yZTogZmxvYXQsIGV4cGxhbmF0aW9uOiBzdHIKICAgICkgLT4gcGEuQW5hbHlzaXNFeHBsYW5hdGlvbjoKICAgICAgICAiIiIKICAgICAgICBDcmVhdGUgZXhwbGFuYXRpb24gZm9yIHdoeSB0aGlzIHJlc3VsdCB3YXMgZGV0ZWN0ZWQuCgogICAgICAgIDpwYXJhbSBvcmlnaW5hbF9zY29yZTogU2NvcmUgZ2l2ZW4gYnkgdGhpcyByZWNvZ25pemVyCiAgICAgICAgOnBhcmFtIGV4cGxhbmF0aW9uOiAgICBFeHBsYW5hdGlvbiBzdHJpbmcKCiAgICAgICAgOnJldHVybnM6IFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24KICAgICAgICAiIiIKCiAgICAgICAgIyBDcmVhdGUgdGhlIFByZXNpZGlvIEFuYWx5c2lzRXhwbGFuYXRpb24gZm9yIHRoZSByZXN1bHQKICAgICAgICBleHBsYW5hdGlvbiA9IHBhLkFuYWx5c2lzRXhwbGFuYXRpb24oCiAgICAgICAgICAgIHJlY29nbml6ZXI9c2VsZi5fX2NsYXNzX18uX19uYW1lX18sCiAgICAgICAgICAgIG9yaWdpbmFsX3Njb3JlPW9yaWdpbmFsX3Njb3JlLAogICAgICAgICAgICB0ZXh0dWFsX2V4cGxhbmF0aW9uPWV4cGxhbmF0aW9uLAogICAgICAgICkKICAgICAgICByZXR1cm4gZXhwbGFuYXRpb24KCiAgICAjIHNhbml0eSBjaGVjayBvZiB0aGUgZW50aXR5IGFuZCBsYWJlbCBiZWZvcmUgcmVjb2duaXRpb24KICAgIEBzdGF0aWNtZXRob2QKICAgIGRlZiBfX2NoZWNrX2xhYmVsKAogICAgICAgIGVudGl0eTogc3RyLCBsYWJlbDogc3RyLCBjaGVja19sYWJlbF9ncm91cHM6IFR1cGxlW1NldCwgU2V0XQogICAgKSAtPiBib29sOgogICAgICAgIHJldHVybiBhbnkoCiAgICAgICAgICAgIGVudGl0eSBpbiBlZ3JwIGFuZCBsYWJlbCBpbiBsZ3JwIGZvciBlZ3JwLCBsZ3JwIGluIGNoZWNrX2xhYmVsX2dyb3VwcwogICAgICAgICkKCgojIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lIGJhc2VkIG9uIHRoZSBtb2RlbApkZWYgX2dldF9hbmFseXplcl9lbmdpbmUoCiAgICBtb2RlbDogc3RyID0gTm9uZSwgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUKKSAtPiBwYS5BbmFseXplckVuZ2luZToKICAgICIiIgogICAgUmV0dXJuIHBhLkFuYWx5emVyRW5naW5lLgoKICAgIDpwYXJhbSBtb2RlbDogVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGVudGl0aWVzOiBUaGUgbGlzdCBvZiBlbnRpdGllcyB0byB1c2UuCgogICAgOnJldHVybnM6IHBhLkFuYWx5emVyRW5naW5lCiAgICAiIiIKICAgICMgcmVjb2duaXplciByZWdpc3RyeSB0aGF0IGNhbiBzdG9yZSBtdWx0aXBsZSByZWNvZ25pemVycwogICAgcmVnaXN0cnkgPSBwYS5SZWNvZ25pemVyUmVnaXN0cnkoKQogICAgaWYgbW9kZWwgPT0gTW9kZWxzLlNQQUNZOgogICAgICAgICMgY3VzdG9tIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICBzcGFjeV9yZWNvZ25pemVyID0gQ3VzdG9tU3BhY3lSZWNvZ25pemVyKCkKICAgICAgICAjIGFkZCB0aGUgY3VzdG9tIGJ1aWxkIHNwYWN5IHJlY29nbml6ZXIKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihzcGFjeV9yZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuRkxBSVI6CiAgICAgICAgIyBwcmUtdHJhaW5lZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgIyBhZGQgdGhlIGN1c3RvbSBidWlsZCBmbGFpciByZWNvZ25pemVyCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoZmxhaXJfcmVjb2duaXplcikKICAgIGVsaWYgbW9kZWwgPT0gTW9kZWxzLlBBVFRFUk46CiAgICAgICAgIyBhZGQgdGhlIHBhdHRlcm4gcmVjb2duaXplcgogICAgICAgIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5ID0gUGF0dGVyblJlY29nbml6ZXJGYWN0b3J5KCkKICAgICAgICBmb3IgcmVjb2duaXplciBpbiBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeS5fY3JlYXRlX3BhdHRlcm5fcmVjb2duaXplcigpOgogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxpZiBtb2RlbCA9PSBNb2RlbHMuV0hPTEU6CiAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgZmxhaXJfcmVjb2duaXplciA9IEZsYWlyUmVjb2duaXplcigpCiAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIoc3BhY3lfcmVjb2duaXplcikKICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgZm9yIHJlY29nbml6ZXIgaW4gcGF0dGVybl9yZWNvZ25pemVyX2ZhY3RvcnkuX2NyZWF0ZV9wYXR0ZXJuX3JlY29nbml6ZXIoKToKICAgICAgICAgICAgcmVnaXN0cnkuYWRkX3JlY29nbml6ZXIocmVjb2duaXplcikKICAgIGVsaWYgbm90IG1vZGVsIGFuZCBlbnRpdGllczoKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgQ3VzdG9tU3BhY3lSZWNvZ25pemVyLlJFQ09HTklaQUJMRV9FTlRJVElFUzoKICAgICAgICAgICAgc3BhY3lfcmVjb2duaXplciA9IEN1c3RvbVNwYWN5UmVjb2duaXplcigpCiAgICAgICAgICAgIHJlZ2lzdHJ5LmFkZF9yZWNvZ25pemVyKHNwYWN5X3JlY29nbml6ZXIpCiAgICAgICAgaWYgc2V0KGVudGl0aWVzKSAmIEZsYWlyUmVjb2duaXplci5SRUNPR05JWkFCTEVfRU5USVRJRVM6CiAgICAgICAgICAgIGZsYWlyX3JlY29nbml6ZXIgPSBGbGFpclJlY29nbml6ZXIoKQogICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihmbGFpcl9yZWNvZ25pemVyKQogICAgICAgICMgYWRkIHRoZSBwYXR0ZXJuIHJlY29nbml6ZXIKICAgICAgICBpZiBzZXQoZW50aXRpZXMpICYgKHNldChQYXR0ZXJuUmVjb2duaXplckZhY3RvcnkuUkVDT0dOSVpBQkxFX0VOVElUSUVTLmtleXMoKSkpOgogICAgICAgICAgICBwYXR0ZXJuX3JlY29nbml6ZXJfZmFjdG9yeSA9IFBhdHRlcm5SZWNvZ25pemVyRmFjdG9yeSgpCiAgICAgICAgICAgIGZvciByZWNvZ25pemVyIGluIHBhdHRlcm5fcmVjb2duaXplcl9mYWN0b3J5Ll9jcmVhdGVfcGF0dGVybl9yZWNvZ25pemVyKCk6CiAgICAgICAgICAgICAgICByZWdpc3RyeS5hZGRfcmVjb2duaXplcihyZWNvZ25pemVyKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmImFyZ3VtZW50IG9mIG1vZGVsIGFuZCBlbnRpdGllcyBjYW4gbm90IGJlIE5vbmUgYXQgdGhlIHNhbWUgdGltZSIKICAgICAgICApCiAgICBhbmFseXplciA9IHBhLkFuYWx5emVyRW5naW5lKAogICAgICAgIHJlZ2lzdHJ5PXJlZ2lzdHJ5LAogICAgICAgIHN1cHBvcnRlZF9sYW5ndWFnZXM9WyJlbiJdLAogICAgKQoKICAgIHN1cHBvcnRlZF9lbnRpdGllcyA9IGFuYWx5emVyLmdldF9zdXBwb3J0ZWRfZW50aXRpZXMoKQoKICAgIGlmIGVudGl0aWVzIGFuZCBub3QgYWxsKGl0ZW0gaW4gc3VwcG9ydGVkX2VudGl0aWVzIGZvciBpdGVtIGluIGVudGl0aWVzKToKICAgICAgICBub3Rfc3VwcG9ydGVkX2VudGl0aWVzID0gWwogICAgICAgICAgICBpdGVtIGZvciBpdGVtIGluIGVudGl0aWVzIGlmIGl0ZW0gbm90IGluIHN1cHBvcnRlZF9lbnRpdGllcwogICAgICAgIF0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBjdXJyZW50IG1vZGVsIHttb2RlbH0gZG9lc24ndCBzdXBwb3J0IHRoZSBmb2xsb3dpbmcgZW50aXRpZXM6IHtub3Rfc3VwcG9ydGVkX2VudGl0aWVzfS4gIgogICAgICAgICAgICBmIlN1cHBvcnRlZCBlbnRpdGllcyBhcmU6IHtzdXBwb3J0ZWRfZW50aXRpZXN9IgogICAgICAgICkKICAgIHJldHVybiBhbmFseXplcgoKCmRlZiBfZ2V0X2Fub255bWl6ZXJfZW5naW5lKCkgLT4gcHJlX2Fub3ltaXplci5Bbm9ueW1pemVyRW5naW5lOgogICAgIiIiCiAgICBSZXR1cm4gQW5vbnltaXplckVuZ2luZS4KCiAgICA6cmV0dXJuczogVGhlIEFub255bWl6ZXJFbmdpbmUuCiAgICAiIiIKICAgIHJldHVybiBwcmVfYW5veW1pemVyLkFub255bWl6ZXJFbmdpbmUoKQoKCmRlZiBfYW5vbnltaXplKAogICAgdGV4dDogc3RyLAogICAgYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLAogICAgZW50aXR5X29wZXJhdG9yX21hcDogZGljdCA9IE5vbmUsCiAgICBpc19mdWxsX3RleHQ6IGJvb2wgPSBUcnVlLAopIC0+IHN0cjoKICAgICIiIgogICAgQW5vbnltaXplIGlkZW50aWZpZWQgaW5wdXQgdXNpbmcgUHJlc2lkaW8gQWJvbnltaXplci4KCiAgICA6cGFyYW0gdGV4dDogICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIGFuYWx5emVfcmVzdWx0czogICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6IFRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwIGlzIGEgZGljdGlvbmFyeSB0aGF0IG1hcHMgZW50aXR5IHRvIG9wZXJhdG9yIG5hbWUgYW5kIG9wZXJhdG9yIHBhcmFtcy4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICBXaGV0aGVyIHRoZSB0ZXh0IGlzIGZ1bGwgdGV4dCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBhbm9ueW1pemVkIHRleHQuCiAgICAiIiIKICAgIGlmIG5vdCB0ZXh0OgogICAgICAgIHJldHVybiAiIgoKICAgIGFub255bWl6ZXJfZW5naW5lID0gX2dldF9hbm9ueW1pemVyX2VuZ2luZSgpCiAgICBpZiBub3QgZW50aXR5X29wZXJhdG9yX21hcDoKICAgICAgICBvcGVyYXRvcnMgPSBOb25lCiAgICBlbHNlOgogICAgICAgICMgQ3JlYXRlIE9wZXJhdG9yQ29uZmlnIGJhc2VkIG9uIHRoZSBlbnRpdHlfb3BlcmF0b3JfbWFwCiAgICAgICAgb3BlcmF0b3JzID0gewogICAgICAgICAgICBlbnRpdHk6IE9wZXJhdG9yQ29uZmlnKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykKICAgICAgICAgICAgZm9yIGVudGl0eSwgKG9wZXJhdG9yX25hbWUsIG9wZXJhdG9yX3BhcmFtcykgaW4gZW50aXR5X29wZXJhdG9yX21hcC5pdGVtcygpCiAgICAgICAgfQoKICAgIGlmIGlzX2Z1bGxfdGV4dDoKICAgICAgICAjIEFub255bWl6ZSB0aGUgZW50aXJlIHRleHQKICAgICAgICByZXR1cm4gYW5vbnltaXplcl9lbmdpbmUuYW5vbnltaXplKAogICAgICAgICAgICB0ZXh0PXRleHQsIGFuYWx5emVyX3Jlc3VsdHM9YW5hbHl6ZV9yZXN1bHRzLCBvcGVyYXRvcnM9b3BlcmF0b3JzCiAgICAgICAgKS50ZXh0CiAgICAjIFRva2VuaXplIHRoZSB0ZXh0IHRvIHNlbnRlbmNlcwogICAgc2VudGVuY2VzID0gbmx0ay5zZW50X3Rva2VuaXplKHRleHQpCiAgICBhbm9ueW1pemVkX3NlbnRlbmNlcyA9IFtdCiAgICBjdXJyZW50X2lkeCA9IDAKCiAgICAjIEZpbmQgdGhlIHNlbnRlbmNlIHRoYXQgaGFzIHBpaSBlbnRpdHkKICAgIGZvciBzZW50ZW5jZSBpbiBzZW50ZW5jZXM6CiAgICAgICAgc3RhcnRfaWR4ID0gY3VycmVudF9pZHgKICAgICAgICBlbmRfaWR4ID0gc3RhcnRfaWR4ICsgbGVuKHNlbnRlbmNlKQoKICAgICAgICAjIEdldCB0aGUgZW50aXRpZXMgdGhhdCBhcmUgaW4gdGhlIHNlbnRlbmNlLCB1cGRhdGUgaHRlIHN0YXJ0X2lkeCBhbmQgZW5kX2lkeAogICAgICAgIHNlbnRlbmNlX3Jlc3VsdHMgPSBbCiAgICAgICAgICAgIHBhLlJlY29nbml6ZXJSZXN1bHQoCiAgICAgICAgICAgICAgICByZXN1bHQuZW50aXR5X3R5cGUsCiAgICAgICAgICAgICAgICBzdGFydD1yZXN1bHQuc3RhcnQgLSBzdGFydF9pZHgsCiAgICAgICAgICAgICAgICBlbmQ9cmVzdWx0LmVuZCAtIHN0YXJ0X2lkeCwKICAgICAgICAgICAgICAgIHNjb3JlPXJlc3VsdC5zY29yZSwKICAgICAgICAgICAgKQogICAgICAgICAgICBmb3IgcmVzdWx0IGluIGFuYWx5emVfcmVzdWx0cwogICAgICAgICAgICBpZiByZXN1bHQuc3RhcnQgPj0gc3RhcnRfaWR4IGFuZCByZXN1bHQuZW5kIDw9IGVuZF9pZHgKICAgICAgICBdCgogICAgICAgICMgSWYgUElJIGlzIGRldGVjdGVkCiAgICAgICAgaWYgc2VudGVuY2VfcmVzdWx0czoKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZSA9IGFub255bWl6ZXJfZW5naW5lLmFub255bWl6ZSgKICAgICAgICAgICAgICAgIHRleHQ9c2VudGVuY2UsIGFuYWx5emVyX3Jlc3VsdHM9c2VudGVuY2VfcmVzdWx0cywgb3BlcmF0b3JzPW9wZXJhdG9ycwogICAgICAgICAgICApLnRleHQKICAgICAgICAgICAgYW5vbnltaXplZF9zZW50ZW5jZXMuYXBwZW5kKGFub255bWl6ZWRfc2VudGVuY2UpCgogICAgICAgIGN1cnJlbnRfaWR4ID0gZW5kX2lkeAoKICAgIHJldHVybiAiICIuam9pbihhbm9ueW1pemVkX3NlbnRlbmNlcykKCgpkZWYgX2dldF90b2tlbnMoCiAgICB0ZXh0OiBzdHIsIGFuYWx5emVfcmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbDogYm9vbCA9IFRydWUKKSAtPiBMaXN0W3N0cl06CiAgICAiIiIKICAgIEdldCB0aGUgZnVsbCB0b2tlbnMgb3Igb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSBhbmFseXplX3Jlc3VsdHM6IFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbQogICAgOnBhcmFtIGlzX2Z1bGw6ICAgICAgICAgV2hldGhlciByZXR1cm4gZnVsbCB0b2tlbnMgb3IganVzdCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlLgoKICAgIDpyZXR1cm5zOiBUaGUgdG9rZW5zLgogICAgIiIiCgogICAgdG9rZW5zID0gW10KICAgICMgc29ydCBieSBzdGFydCBpbmRleAogICAgcmVzdWx0cyA9IHNvcnRlZChhbmFseXplX3Jlc3VsdHMsIGtleT1sYW1iZGEgeDogeC5zdGFydCkKICAgIGZvciBpLCByZXMgaW4gZW51bWVyYXRlKHJlc3VsdHMpOgogICAgICAgIGlmIGkgPT0gMDoKICAgICAgICAgICAgdG9rZW5zLmFwcGVuZCh0ZXh0WzogcmVzLnN0YXJ0XSkKCiAgICAgICAgIyBhcHBlbmQgZW50aXR5IHRleHQgYW5kIGVudGl0eSB0eXBlCiAgICAgICAgdG9rZW5zLmFwcGVuZCgodGV4dFtyZXMuc3RhcnQgOiByZXMuZW5kXSwgcmVzLmVudGl0eV90eXBlKSkKCiAgICAgICAgIyBpZiBhbm90aGVyIGVudGl0eSBjb21pbmcgaS5lLiB3ZSdyZSBub3QgYXQgdGhlIGxhc3QgcmVzdWx0cyBlbGVtZW50LAogICAgICAgICMgYWRkIHRleHQgdXAgdG8gbmV4dCBlbnRpdHkKICAgICAgICBpZiBpICE9IGxlbihyZXN1bHRzKSAtIDE6CiAgICAgICAgICAgIHRva2Vucy5hcHBlbmQodGV4dFtyZXMuZW5kIDogcmVzdWx0c1tpICsgMV0uc3RhcnRdKQogICAgICAgICMgaWYgbm8gbW9yZSBlbnRpdGllcyBjb21pbmcsIGFkZCBhbGwgcmVtYWluaW5nIHRleHQKICAgICAgICBlbHNlOgogICAgICAgICAgICB0b2tlbnMuYXBwZW5kKHRleHRbcmVzLmVuZCA6XSkKCiAgICAjIGdldCB0aGUgdG9rZW5zIHRoYXQgb25seSBjb250YWlucyB0aGUgZW50aXRpZXMgdGhhdCBjYW4gZm9ybSBhIHNlbnRlbmNlCiAgICBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zID0gW10KICAgIGlmIG5vdCBpc19mdWxsOgogICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gMAogICAgICAgIGZvciBpLCB0b2tlbiBpbiBlbnVtZXJhdGUodG9rZW5zKToKICAgICAgICAgICAgaWYgYW55KGl0ZW0gaW4gdG9rZW4gZm9yIGl0ZW0gaW4gWyIuIiwgIiEiLCAiPyJdKSBhbmQgYW55KAogICAgICAgICAgICAgICAgdHlwZShpdGVtKSBpcyB0dXBsZSBmb3IgaXRlbSBpbiB0b2tlbnNbbGFzdF9lbmRfc2VudGVuY2U6aV0KICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgIHBhcnRfYW5ub250YXRlZF90b2tlbnMuYXBwZW5kKHRva2Vuc1tsYXN0X2VuZF9zZW50ZW5jZTppXSkKICAgICAgICAgICAgICAgIGxhc3RfZW5kX3NlbnRlbmNlID0gaQogICAgICAgIHJldHVybiBwYXJ0X2Fubm9udGF0ZWRfdG9rZW5zCiAgICByZXR1cm4gdG9rZW5zCgoKZGVmIF9hbm5vdGF0ZSgKICAgIHRleHQ6IHN0ciwgc3RfYW5hbHl6ZV9yZXN1bHRzOiBMaXN0W3BhLlJlY29nbml6ZXJSZXN1bHRdLCBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlCikgLT4gTGlzdFtzdHJdOgogICAgIiIiCiAgICBBbm5vdGF0ZSBpZGVudGlmaWVkIGlucHV0IHVzaW5nIFByZXNpZGlvIEFub255bWl6ZXIuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgVGhlIHRleHQgZm9yIGFuYWx5c2lzLgogICAgOnBhcmFtIHN0X2FuYWx5emVfcmVzdWx0czogVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfaHRtbDogICAgICAgV2hldGhlciBnZW5lcmF0ZSBmdWxsIGh0bWwgb3Igbm90LgoKICAgIDpyZXR1cm5zOiBUaGUgbGlzdCBvZiB0b2tlbnMgd2l0aCB0aGUgaWRlbnRpZmllZCBlbnRpdGllcy4KCiAgICAiIiIKICAgIHJldHVybiBfZ2V0X3Rva2Vucyh0ZXh0LCBzdF9hbmFseXplX3Jlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKCgpkZWYgX3Byb2Nlc3MoCiAgICB0ZXh0OiBzdHIsCiAgICBtb2RlbDogcGEuQW5hbHl6ZXJFbmdpbmUsCiAgICBzY29yZV90aHJlc2hvbGQ6IGZsb2F0LAogICAgZW50aXRpZXM6IExpc3Rbc3RyXSA9IE5vbmUsCiAgICBlbnRpdGllc19vcGVyYXRvcl9tYXA6IGRpY3QgPSBOb25lLAogICAgaXNfZnVsbF90ZXh0OiBib29sID0gVHJ1ZSwKKSAtPiBUdXBsZVtzdHIsIGxpc3RdOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSB0ZXh0IG9mIHN0ciB1c2luZyB0aGUgbW9kZWwuCgogICAgOnBhcmFtIHRleHQ6ICAgICAgICAgICAgICAgICAgVGV4dCB0byBwcm9jZXNzCiAgICA6cGFyYW0gbW9kZWw6ICAgICAgICAgICAgICAgICBNb2RlbCB0byB1c2UgZm9yIHByb2Nlc3NpbmcKICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgIEVudGl0aWVzIHRvIHJlY29nbml6ZQogICAgOnBhcmFtIGVudGl0aWVzX29wZXJhdG9yX21hcDogVGhlIGVudGl0eV9vcGVyYXRvcl9tYXAgaXMgYSBkaWN0aW9uYXJ5IHRoYXQgbWFwcyBlbnRpdHkgdG8gb3BlcmF0b3IgbmFtZSBhbmQgb3BlcmF0b3IgcGFyYW1zLgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICAgVGhlIHNjb3JlIHRocmVzaG9sZCB0byB1c2UgZm9yIHJlY29nbml0aW9uCiAgICA6cGFyYW0gaXNfZnVsbF90ZXh0OiAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCB0ZXh0IG9yIGp1c3QgdGhlIGFubm90YXRlZCB0ZXh0CgogICAgOnJldHVybnM6IEEgdHVwbGUgb2Y6CgogICAgICAgICAgICAgICogdGhlIGFub255bWl6ZWQgdGV4dAogICAgICAgICAgICAgICogdGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzCiAgICAiIiIKCiAgICAjIGdldCB0aGUgYW5hbHl6ZXIgZW5naW5lCiAgICBhbmFseXplciA9IG1vZGVsCgogICAgIyBhbmFseXplIHRoZSB0ZXh0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIGFub255bWl6YXRpb24KICAgIHJlc3VsdHMgPSBhbmFseXplci5hbmFseXplKAogICAgICAgIHRleHQ9dGV4dCwKICAgICAgICBsYW5ndWFnZT0iZW4iLAogICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgIHNjb3JlX3RocmVzaG9sZD1zY29yZV90aHJlc2hvbGQsCiAgICAgICAgcmV0dXJuX2RlY2lzaW9uX3Byb2Nlc3M9VHJ1ZSwKICAgICkKCiAgICAjIGFub255bWl6ZSB0aGUgdGV4dCwgcmVwbGFjZSB0aGUgcGlpIGVudGl0aWVzIHdpdGggdGhlIGxhYmVscwogICAgYW5vbnltaXplZF90ZXh0ID0gX2Fub255bWl6ZSh0ZXh0LCByZXN1bHRzLCBlbnRpdGllc19vcGVyYXRvcl9tYXAsIGlzX2Z1bGxfdGV4dCkKCiAgICByZXR1cm4gYW5vbnltaXplZF90ZXh0LCByZXN1bHRzCgoKZGVmIF9nZXRfc2luZ2xlX2h0bWwoCiAgICB0ZXh0OiBzdHIsIHJlc3VsdHM6IExpc3RbcGEuUmVjb2duaXplclJlc3VsdF0sIGlzX2Z1bGxfaHRtbDogYm9vbCA9IFRydWUKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSB0ZXh0OiAgICAgICAgIFRoZSB0ZXh0IGZvciBhbmFseXNpcy4KICAgIDpwYXJhbSByZXN1bHRzOiAgICAgIFRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhIHNpbmdsZSB0eHQgZmlsZS4KICAgICIiIgogICAgIyBjb252ZXJ0IHRoZSByZXN1bHRzIHRvIHRva2VucyB0byBnZW5lcmF0ZSB0aGUgaHRtbAogICAgdG9rZW5zID0gX2Fubm90YXRlKHRleHQsIHJlc3VsdHMsIGlzX2Z1bGxfaHRtbCkKICAgIGh0bWwgPSBhdF91dGlsLmdldF9hbm5vdGF0ZWRfaHRtbCgqdG9rZW5zKQoKICAgICMgYXZvaWQgdGhlIGVycm9yIGR1cmluZyByZW5kZXJpbmcgb2YgdGhlIFxuIGluIHRoZSBodG1sCiAgICBiYWNrc2xhc2hfY2hhciA9ICJcXCIKCiAgICBodG1sX3N0ciA9IGYiPHA+e2h0bWwucmVwbGFjZSgne2JhY2tzbGFzaF9jaGFyfW4nLCAnPGJyPicpfTwvcD4iCgogICAgcmV0dXJuIGh0bWxfc3RyCgoKZGVmIF9nZXRfc2luZ2xlX2pzb24ocmVzdWx0czogTGlzdFtwYS5SZWNvZ25pemVyUmVzdWx0XSwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGpzb24gZm9yIGEgc2luZ2xlIHR4dCBmaWxlLgoKICAgIDpwYXJhbSByZXN1bHRzOiAgICAgICAgVGhlIGxpc3Qgb2YgUHJlc2lkaW8gUmVjb2duaXplclJlc3VsdCBjb25zdHJ1Y3RlZCBmcm9tIGFuYWx5c2lzLgogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiBXaGV0aGVyIGdlbmVyYXRlIGZ1bGwganNvbiBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBqc29uIHN0cmluZyBmb3IgYSBzaW5nbGUgdHh0IGZpbGUuCiAgICAiIiIKICAgICMgZ2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBpZiBuZWVkZWQKICAgIGlmIG5vdCBpc19mdWxsX3JlcG9ydDoKICAgICAgICBzdGF0cyA9IFtdCiAgICAgICAgIyBhZGQgdGhlIHNpbXBsaWZ5IHN0YXRzIGxvZ2ljIGhlcmUKICAgICAgICBmb3IgaXRlbSBpbiByZXN1bHRzOgogICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gTm9uZQogICAgICAgICAgICBzdGF0cy5hcHBlbmQoaXRlbSkKICAgIGVsc2U6CiAgICAgICAgc3RhdHMgPSByZXN1bHRzCgogICAgcmV0dXJuIHN0YXRzCgoKZGVmIF9nZXRfYWxsX2h0bWwoCiAgICB0eHRfY29udGVudDogZGljdCwKICAgIHJlc19kaWN0OiBkaWN0LAogICAgaXNfZnVsbF9odG1sOiBib29sID0gVHJ1ZSwKKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIGh0bWwgZm9yIGFsbCB0eHQgZmlsZXMuCgogICAgOnBhcmFtIHR4dF9jb250ZW50OiAgVGhlIGRpY3Rpb25hcnkgb2YgdHh0IGZpbGUgbmFtZSBhbmQgY29udGVudC4KICAgIDpwYXJhbSByZXNfZGljdDogICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6IFdoZXRoZXIgZ2VuZXJhdGUgZnVsbCBodG1sIG9yIG5vdC4KCiAgICA6cmV0dXJuczogVGhlIGh0bWwgc3RyaW5nIGZvciBhbGwgdHh0IGZpbGVzLgoKICAgICIiIgogICAgIyBUaGVzZSBhcmUgcGxhY2Vob2xkZXIgZm9yIHRoZSBodG1sIHN0cmluZwogICAgaHRtbF9pbmRleCA9ICI8aHRtbD48aGVhZD48dGl0bGU+SGlnaGxpZ2h0ZWQgUGlpIEVudGl0aWVzPC90aXRsZT48L2hlYWQ+PGJvZHk+PGgxPkhpZ2hsaWdodGVkIFBpaSBFbnRpdGllczwvaDE+PHVsPiIKICAgIGh0bWxfY29udGVudCA9ICIiCiAgICBmb3IgdHh0X2ZpbGUsIHJlc3VsdHMgaW4gcmVzX2RpY3QuaXRlbXMoKToKICAgICAgICB0eHQgPSB0eHRfY29udGVudFt0eHRfZmlsZV0KICAgICAgICBodG1sX2luZGV4ICs9IGYiPGxpPjxhIGhyZWY9JyN7dHh0X2ZpbGV9Jz57dHh0X2ZpbGV9PC9hPjwvbGk+IgogICAgICAgIGh0bWxfY29udGVudCArPSBmIjxsaT48aDI+e3R4dF9maWxlfTwvaDI+PHA+e19nZXRfc2luZ2xlX2h0bWwodHh0LCByZXN1bHRzLCBpc19mdWxsX2h0bWwpfTwvcD48L2xpPiIKICAgIGh0bWxfaW5kZXggKz0gIjwvdWw+IgogICAgaHRtbF9yZXMgPSBmIntodG1sX2luZGV4fXtodG1sX2NvbnRlbnR9PC9ib2R5PjwvaHRtbD4iCgogICAgcmV0dXJuIGh0bWxfcmVzCgoKZGVmIF9nZXRfYWxsX3JwdChyZXNfZGljdDogZGljdCwgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlKToKICAgICIiIgogICAgR2VuZXJhdGUgdGhlIHN0YXRzIHJlcG9ydCBmb3IgYWxsIHR4dCBmaWxlcy4KCiAgICA6cGFyYW0gcmVzX2RpY3Q6ICAgICAgIFRoZSBkaWN0aW9uYXJ5IG9mIHR4dCBmaWxlIG5hbWUgYW5kIHRoZSBsaXN0IG9mIFByZXNpZGlvIFJlY29nbml6ZXJSZXN1bHQgY29uc3RydWN0ZWQgZnJvbSBhbmFseXNpcy4KICAgIDpwYXJhbSBpc19mdWxsX3JlcG9ydDogV2hldGhlciBnZW5lcmF0ZSBmdWxsIHJlcG9ydCBvciBub3QuCgogICAgOnJldHVybnM6IFRoZSBzdGF0cyByZXBvcnQgZm9yIGFsbCB0eHQgZmlsZXMuCiAgICAiIiIKICAgICMgVGhlc2UgYXJlIHBsYWNlaG9sZGVyIGZvciB0aGUganNvbiByZXBvcnQKICAgIHN0YXRzX2RpY3QgPSB7fQogICAgZm9yIHR4dF9maWxlLCByZXN1bHRzIGluIHJlc19kaWN0Lml0ZW1zKCk6CiAgICAgICAgbmV3X3N0YXRzID0gW10KICAgICAgICBmb3IgaXRlbSBpbiBfZ2V0X3NpbmdsZV9qc29uKHJlc3VsdHMsIGlzX2Z1bGxfcmVwb3J0KToKICAgICAgICAgICAgaWYgaXNfZnVsbF9yZXBvcnQ6CiAgICAgICAgICAgICAgICBpdGVtLmFuYWx5c2lzX2V4cGxhbmF0aW9uID0gaXRlbS5hbmFseXNpc19leHBsYW5hdGlvbi50b19kaWN0KCkKICAgICAgICAgICAgICAgIG5ld19zdGF0cy5hcHBlbmQoaXRlbS50b19kaWN0KCkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB0bXBfZGljdCA9IGl0ZW0udG9fZGljdCgpCiAgICAgICAgICAgICAgICB0bXBfZGljdC5wb3AoImFuYWx5c2lzX2V4cGxhbmF0aW9uIikKICAgICAgICAgICAgICAgIHRtcF9kaWN0LnBvcCgicmVjb2duaXRpb25fbWV0YWRhdGEiKQogICAgICAgICAgICAgICAgbmV3X3N0YXRzLmFwcGVuZCh0bXBfZGljdCkKICAgICAgICBzdGF0c19kaWN0W3R4dF9maWxlXSA9IG5ld19zdGF0cwogICAgcmV0dXJuIHN0YXRzX2RpY3QKCgpkZWYgcmVjb2duaXplX3BpaSgKICAgIGNvbnRleHQ6IG1scnVuLk1MQ2xpZW50Q3R4LAogICAgaW5wdXRfcGF0aDogVW5pb25bc3RyLCBwYXRobGliLlBhdGhdLAogICAgaHRtbF9rZXk6IHN0ciwKICAgIHNjb3JlX3RocmVzaG9sZDogZmxvYXQsCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIgPSBOb25lLAogICAgZW50aXRpZXM6IExpc3RbCiAgICAgICAgc3RyCiAgICBdID0gTm9uZSwgICMgTGlzdCBvZiBlbnRpdGllcyB0byByZWNvZ25pemUsIGRlZmF1bHQgaXMgcmVjb2duaXppbmcgYWxsCiAgICBlbnRpdHlfb3BlcmF0b3JfbWFwOiBkaWN0ID0gTm9uZSwKICAgIG1vZGVsOiBzdHIgPSBOb25lLAogICAgZ2VuZXJhdGVfanNvbjogYm9vbCA9IFRydWUsCiAgICBnZW5lcmF0ZV9odG1sOiBib29sID0gVHJ1ZSwKICAgIGlzX2Z1bGxfdGV4dDogYm9vbCA9IFRydWUsCiAgICBpc19mdWxsX2h0bWw6IGJvb2wgPSBUcnVlLAogICAgaXNfZnVsbF9yZXBvcnQ6IGJvb2wgPSBUcnVlLAopIC0+IFVuaW9uW1R1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0LCBkaWN0XSwgVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdXToKICAgICIiIgogICAgV2FsayB0aHJvdWdoIHRoZSBpbnB1dCBwYXRoLCByZWNvZ25pemUgUElJIGluIHRleHQgYW5kIHN0b3JlIHRoZSBhbm9ueW1pemVkIHRleHQgaW4gdGhlIG91dHB1dCBwYXRoLgogICAgR2VuZXJhdGUgdGhlIGh0bWwgd2l0aCBkaWZmZXJlbnQgY29sb3JzIGZvciBlYWNoIGVudGl0eSwganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uLgoKICAgIDpwYXJhbSBjb250ZXh0OiAgICAgICAgICAgICAgVGhlIE1MUnVuIGNvbnRleHQuIHRoaXMgaXMgbmVlZGVkIGZvciBsb2cgdGhlIGFydGlmYWN0cy4KICAgIDpwYXJhbSBpbnB1dF9wYXRoOiAgICAgICAgICAgVGhlIGlucHV0IHBhdGggb2YgdGhlIHRleHQgZmlsZXMgbmVlZHMgdG8gYmUgYW5hbHl6ZWQuCiAgICA6cGFyYW0gaHRtbF9rZXk6ICAgICAgICAgICAgIFRoZSBodG1sIGtleSBmb3IgdGhlIGFydGlmYWN0LgogICAgOnBhcmFtIHNjb3JlX3RocmVzaG9sZDogICAgICBUaGUgc2NvcmUgdGhyZXNob2xkIHRvIG1hcmsgdGhlIHJlY29nbml0aW9uIGFzIHRydXN0ZWQuCiAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogICAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGggdG8gc3RvcmUgdGhlIGFub255bWl6ZWQgdGV4dC4KICAgIDpwYXJhbSBlbnRpdGllczogICAgICAgICAgICAgVGhlIGxpc3Qgb2YgZW50aXRpZXMgdG8gcmVjb2duaXplLgogICAgOnBhcmFtIGVudGl0eV9vcGVyYXRvcl9tYXA6ICBUaGUgbWFwIG9mIGVudGl0eSB0byBvcGVyYXRvciAobWFzaywgcmVkYWN0LCByZXBsYWNlLCBrZWVwLCBoYXNoLCBhbmQgaXRzIHBhcmFtcykKICAgIDpwYXJhbSBtb2RlbDogICAgICAgICAgICAgICAgVGhlIG1vZGVsIHRvIHVzZS4gQ2FuIGJlICJzcGFjeSIsICJmbGFpciIsICJwYXR0ZXJuIiBvciAid2hvbGUiLgogICAgOnBhcmFtIGdlbmVyYXRlX2pzb246ICAgICAgICBXaGV0aGVyIHRvIGdlbmVyYXRlIHRoZSBqc29uIHJlcG9ydCBvZiB0aGUgZXhwbGFuYXRpb24uCiAgICA6cGFyYW0gZ2VuZXJhdGVfaHRtbDogICAgICAgIFdoZXRoZXIgdG8gZ2VuZXJhdGUgdGhlIGh0bWwgcmVwb3J0IG9mIHRoZSBleHBsYW5hdGlvbi4KICAgIDpwYXJhbSBpc19mdWxsX3RleHQ6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgdGV4dCBvciBvbmx5IHRoZSBtYXNrZWQgdGV4dC4KICAgIDpwYXJhbSBpc19mdWxsX2h0bWw6ICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGhlIGZ1bGwgaHRtbCBvciBqdXN0IHRoZSBhbm5vdGF0ZWQgdGV4dAogICAgOnBhcmFtIGlzX2Z1bGxfcmVwb3J0OiAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aGUgZnVsbCByZXBvcnQgb3IganVzdCB0aGUgc2NvcmUgYW5kIHN0YXJ0LCBlbmQgaW5kZXgKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBQYXRoIHRvIHRoZSBvdXRwdXQgZGlyZWN0b3J5CiAgICAgICAgICAgICAgKiBUaGUganNvbiByZXBvcnQgb2YgdGhlIGV4cGxhbmF0aW9uIChpZiBnZW5lcmF0ZV9qc29uIGlzIFRydWUpCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JzIGZpbGVzIHRoYXQgd2VyZSBub3QgcHJvY2Vzc2VkCgogICAgIiIiCgogICAgIyBTZXQgb3V0cHV0IGRpcmVjdG9yeQogICAgaWYgb3V0cHV0X2RpcmVjdG9yeSBpcyBOb25lOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkgPSB0ZW1wZmlsZS5ta2R0ZW1wKCkKCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIG91dHB1dF9kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgob3V0cHV0X2RpcmVjdG9yeSkKICAgIGlmIG5vdCBvdXRwdXRfZGlyZWN0b3J5LmV4aXN0cygpOgogICAgICAgIG91dHB1dF9kaXJlY3RvcnkubWtkaXIocGFyZW50cz1UcnVlLCBleGlzdF9vaz1UcnVlKQoKICAgIHR4dF9maWxlc19kaXJlY3RvcnkgPSBwYXRobGliLlBhdGgoaW5wdXRfcGF0aCkKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgIHJlc19kaWN0ID0ge30KICAgIHR4dF9jb250ZW50ID0ge30KICAgICMgTG9hZCB0aGUgbW9kZWw6CiAgICBhbmFseXplciA9IF9nZXRfYW5hbHl6ZXJfZW5naW5lKG1vZGVsLCBlbnRpdGllcykKICAgIGxvZ2dlci5pbmZvKCJNb2RlbCBsb2FkZWQiKQogICAgIyBHbyBvdmVyIHRoZSB0ZXh0IGZpbGVzIGluIHRoZSBpbnB1dCBwYXRoLCBhbmFseXplIGFuZCBhbm9ueW1pemUgdGhlbToKICAgIGZvciB0eHRfZmlsZSBpbiB0cWRtKAogICAgICAgIGxpc3QodHh0X2ZpbGVzX2RpcmVjdG9yeS5nbG9iKCIqLnR4dCIpKSwKICAgICAgICBkZXNjPSJQcm9jZXNzaW5nIGZpbGVzIiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgdGhlIHN0ciBmcm9tIHRoZSB0ZXh0IGZpbGUKICAgICAgICAgICAgdGV4dCA9IHR4dF9maWxlLnJlYWRfdGV4dCgpCiAgICAgICAgICAgIHR4dF9jb250ZW50W3N0cih0eHRfZmlsZSldID0gdGV4dAogICAgICAgICAgICAjIFByb2Nlc3MgdGhlIHRleHQgdG8gcmVjb2dpbnplIHRoZSBwaWkgZW50aXRpZXMgaW4gaXQKICAgICAgICAgICAgYW5vbnltaXplZF90ZXh0LCByZXN1bHRzID0gX3Byb2Nlc3MoCiAgICAgICAgICAgICAgICB0ZXh0PXRleHQsCiAgICAgICAgICAgICAgICBtb2RlbD1hbmFseXplciwKICAgICAgICAgICAgICAgIGVudGl0aWVzPWVudGl0aWVzLAogICAgICAgICAgICAgICAgZW50aXRpZXNfb3BlcmF0b3JfbWFwPWVudGl0eV9vcGVyYXRvcl9tYXAsCiAgICAgICAgICAgICAgICBzY29yZV90aHJlc2hvbGQ9c2NvcmVfdGhyZXNob2xkLAogICAgICAgICAgICAgICAgaXNfZnVsbF90ZXh0PWlzX2Z1bGxfdGV4dCwKICAgICAgICAgICAgKQogICAgICAgICAgICByZXNfZGljdFtzdHIodHh0X2ZpbGUpXSA9IHJlc3VsdHMKICAgICAgICAgICAgIyBTdG9yZSB0aGUgYW5vbnltaXplZCB0ZXh0IGluIHRoZSBvdXRwdXQgcGF0aAogICAgICAgICAgICBvdXRwdXRfZmlsZSA9IG91dHB1dF9kaXJlY3RvcnkgLyBmInt0eHRfZmlsZS5zdGVtfS50eHQiCiAgICAgICAgICAgIG91dHB1dF9maWxlLnBhcmVudC5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCiAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZmlsZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgZi53cml0ZShhbm9ueW1pemVkX3RleHQpCiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQoW3R4dF9maWxlLm5hbWUsIG91dHB1dF9maWxlLm5hbWVdKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgZXJyb3JzW3N0cih0eHRfZmlsZSldID0gc3RyKGUpCiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihmIkVycm9yIHByb2Nlc3Npbmcge3R4dF9maWxlfToge2V9IikKCiAgICBzdWNjZXNzZXMgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgc3VjY2Vzc2VzLAogICAgICAgIGNvbHVtbnM9WyJvcmlnaW5hbF9maWxlIiwgImFub255bWl6ZWRfZmlsZSJdLAogICAgKQoKICAgIGlmIGdlbmVyYXRlX2h0bWw6CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUgaHRtbCByZXBvcnQKICAgICAgICBodG1sX3JlcyA9IF9nZXRfYWxsX2h0bWwodHh0X2NvbnRlbnQsIHJlc19kaWN0LCBpc19mdWxsX2h0bWwpCiAgICAgICAgIyBTdG9yZSB0aGUgaHRtbCByZXBvcnQgaW4gdGhlIGNvbnRleHQKICAgICAgICBhcnRpX2h0bWwgPSBtbHJ1bi5hcnRpZmFjdHMuQXJ0aWZhY3QoYm9keT1odG1sX3JlcywgZm9ybWF0PSJodG1sIiwga2V5PWh0bWxfa2V5KQogICAgICAgIGNvbnRleHQubG9nX2FydGlmYWN0KGFydGlfaHRtbCkKICAgIGlmIGdlbmVyYXRlX2pzb246CiAgICAgICAgIyBHZW5lcmF0ZSB0aGUganNvbiByZXBvcnQKICAgICAgICBqc29uX3JlcyA9IF9nZXRfYWxsX3JwdChyZXNfZGljdCwgaXNfZnVsbF9yZXBvcnQpCiAgICAgICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMsIGpzb25fcmVzCiAgICByZXR1cm4gc3RyKG91dHB1dF9kaXJlY3RvcnkpLCBzdWNjZXNzZXMsIGVycm9ycwo=
    +    code_origin: ''
    +    origin_filename: ''
       description: This function is used to recognize PII in a directory of text files
    -  default_handler: recognize_pii
    +  image: ''
    +  command: ''
       disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    -verbose: false
    +kind: job
    +metadata:
    +  name: pii-recognizer
    +  tag: ''
    +  categories:
    +  - data-preparation
    +  - NLP
     
             
         
    diff --git a/functions/master/pii_recognizer/latest/static/item.html b/functions/master/pii_recognizer/latest/static/item.html index 1f2995a3..d16c5239 100644 --- a/functions/master/pii_recognizer/latest/static/item.html +++ b/functions/master/pii_recognizer/latest/static/item.html @@ -30,7 +30,6 @@ apiVersion: v1 categories: - - machine-learning - data-preparation - NLP description: This function is used to recognize PII in a directory of text files @@ -43,7 +42,7 @@ author: pgw maintainers: [] marketplaceType: '' -mlrunVersion: 1.4.0 +mlrunVersion: 1.7.0 name: pii-recognizer platformVersion: 3.5.3 spec: @@ -61,7 +60,7 @@ - st-annotated-text - https://huggingface.co/beki/en_spacy_pii_distilbert/resolve/main/en_spacy_pii_distilbert-any-py3-none-any.whl url: '' -version: 0.3.0 +version: 0.4.0 test_valid: False diff --git a/functions/master/pii_recognizer/latest/static/pii_recognizer.html b/functions/master/pii_recognizer/latest/static/pii_recognizer.html index 161e26b9..d271919b 100644 --- a/functions/master/pii_recognizer/latest/static/pii_recognizer.html +++ b/functions/master/pii_recognizer/latest/static/pii_recognizer.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pyannote_audio/1.3.0/src/assets/test_data.wav b/functions/master/pyannote_audio/1.3.0/src/assets/test_data.wav new file mode 100644 index 00000000..a3a993c2 Binary files /dev/null and b/functions/master/pyannote_audio/1.3.0/src/assets/test_data.wav differ diff --git a/functions/master/pyannote_audio/1.3.0/src/function.yaml b/functions/master/pyannote_audio/1.3.0/src/function.yaml new file mode 100644 index 00000000..b4cd9ad9 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/src/function.yaml @@ -0,0 +1,133 @@ +kind: job +spec: + command: '' + disable_auto_mount: false + image: '' + build: + code_origin: '' + requirements: + - pyannote.audio + - pyannote.core + - torchaudio + - tqdm + base_image: mlrun/mlrun-gpu + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo= + default_handler: diarize + entry_points: + open_mpi_handler: + name: open_mpi_handler + has_varargs: false + lineno: 61 + parameters: + - name: worker_inputs + type: List[str] + - name: root_worker_inputs + type: Dict[str, Any] + default: null + has_kwargs: false + doc: '' + decorator: + name: decorator + has_varargs: false + lineno: 73 + parameters: + - name: handler + has_kwargs: false + doc: '' + wrapper: + name: wrapper + has_varargs: false + lineno: 78 + has_kwargs: true + doc: '' + diarize: + name: diarize + has_varargs: false + lineno: 139 + outputs: + - doc: 'A tuple of:' + type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]] + parameters: + - name: data_path + type: Union[str, List[str]] + doc: A directory of the audio files, a single file or a list of files to transcribe. + - name: model_name + type: str + doc: 'One of the official diarization model names (referred as diarization + pipelines) of `pyannote.audio` Huggingface page. Default: "pyannote/speaker-diarization-3.0".' + default: pyannote/speaker-diarization-3.0 + - name: access_token + type: str + doc: An access token to pass for using the `pyannote.audio` models. If not + provided, it will be looking for the environment variable "HUGGING_FACE_HUB_TOKEN". + If MLRun is available, it will look for a secret "HUGGING_FACE_HUB_TOKEN". + default: null + - name: device + type: str + doc: Device to load the model. Can be one of {"cuda", "cpu"}. Default will + prefer "cuda" if available. + default: null + - name: speakers_labels + type: List[str] + doc: 'Labels to use for the recognized speakers. Default: numeric labels (0, + 1, ...).' + default: null + - name: speaker_prefix + type: str + doc: 'A prefix to add for the speakers labels. This parameter is ignored if + `speakers_labels` is not None. Default: "speaker".' + default: speaker_ + - name: separate_by_channels + type: bool + doc: If each speaker is speaking in a separate channel, you can diarize each + channel and combine the result into a single diarization. Each label set + in the `speakers_labels` parameter will be assigned to a specific channel + by order. + default: false + - name: minimum_speakers + type: int + doc: Set the minimum expected amount of speakers to be in the audio files. + This parameter is ignored if `speakers_labels` is not None. + default: null + - name: maximum_speakers + type: int + doc: Set the maximum expected amount of speakers to be in the audio files. + This parameter is ignored if `speakers_labels` is not None. + default: null + - name: verbose + type: bool + doc: 'Whether to present logs of a progress bar and errors. Default: True.' + default: false + has_kwargs: false + doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\ + The end result is a dictionary with the file names as keys and their diarization\ + \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\ + \nTo use the `pyannote.audio` models you must pass a Huggingface token and\ + \ get access to the required models. The\ntoken can be passed in one of the\ + \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\ + \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\ + \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\ + \ models on Huggingface, visit their page. For example, to use the default\ + \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\ + ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\ + * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\ + \ the recognized speakers in the diarization output you can choose one of\ + \ the following methods:\n\n* For a known speakers amount, you may set speaker\ + \ labels via the `speakers_labels` parameter that will be used in\n the order\ + \ of speaking in the audio (first person speaking be the first label in the\ + \ list). In addition, you can do\n diarization per channel (setting the parameter\ + \ `separate_by_channels` to True). Each label will be assigned to a\n specific\ + \ channel by order (first label to channel 0, second label to channel 1 and\ + \ so on). Notice, this will\n increase runtime.\n* For unknown speakers amount,\ + \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\ + \ number.\n You can also help the diarization by setting the speakers range\ + \ via the `speakers_amount_range` parameter." + description: pyannote's speech diarization of audio files +metadata: + name: pyannote-audio + tag: '' + categories: + - deep-learning + - audio +verbose: false diff --git a/functions/master/pyannote_audio/1.3.0/src/item.yaml b/functions/master/pyannote_audio/1.3.0/src/item.yaml new file mode 100644 index 00000000..b6dbccdd --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/src/item.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +categories: +- deep-learning +- audio +description: pyannote's speech diarization of audio files +doc: '' +example: pyannote_audio.ipynb +generationDate: 2023-12-03:14-30 +hidden: false +icon: '' +labels: + author: guyl +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: pyannote-audio +platformVersion: 3.5.3 +spec: + filename: pyannote_audio.py + handler: diarize + image: mlrun/mlrun-gpu + kind: job + requirements: + - pyannote.audio + - pyannote.core + - torchaudio + - tqdm +url: '' +version: 1.3.0 diff --git a/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.ipynb b/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.ipynb new file mode 100644 index 00000000..9901cc4f --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4f17e477-db37-41b6-a76e-c69dbeea53db", + "metadata": {}, + "source": [ + "# Speech diarization example notebook" + ] + }, + { + "cell_type": "markdown", + "id": "46e7131b-42fe-4f3c-a268-08d6d4ff9cdf", + "metadata": {}, + "source": [ + "In this notebook we will utilize a call diarization capability to get per-speaker speech durations from a call recording.
    \n", + "This can be useful for quantifying participation rates in calls for things like customer service analysis.
    \n", + "\n", + "We will demonstrate this by:
    \n", + "\n", + "1. Loading in a sample call recording between multiple participants\n", + "2. Using a diarize() function to automatically detect speakers and estimate per-speaker talk time\n", + "3. Return a dictionary of described results, and a df of errors\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "53d25661-15eb-40c0-8ec8-4af9838c1d04", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import mlrun" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "68b84d16-d0aa-4e86-a716-5d92e38c9236", + "metadata": {}, + "outputs": [], + "source": [ + "# To use the `pyannote.audio` models you must pass a Huggingface token and get access to the required models. The\n", + "# token can be passed in one of the following options:\n", + "#\n", + "# * Use the parameter `access_token`.\n", + "# * Set an environment variable named \"HUGGING_FACE_HUB_TOKEN\".\n", + "# * If using MLRun, you can pass it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n", + "os.environ[\"HUGGING_FACE_HUB_TOKEN\"] = <\"add your token here\">\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2a0b1f97-6fba-400f-aacf-fe1da28e35d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-05 15:28:51,758 [info] Project loaded successfully: {'project_name': 'diarization-test'}\n" + ] + } + ], + "source": [ + "# Create an mlrun project\n", + "project = mlrun.get_or_create_project(\"diarization-test\")\n", + "\n", + "# Import the function from the yaml file, once it's in the the we can import from there \n", + "speech_diarization = project.set_function(func=\"hub://speech_diarization\", name=\"speech_diarization\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "50d9a797-a3f2-4824-b6e2-8245f6e30b17", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the desired run params and files\n", + "audio_files = os.path.join(\"test_data.wav\")\n", + "device = \"cpu\"\n", + "speakers_labels = [\"Agent\", \"Client\"]\n", + "separate_by_channels = True" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "110080e5-3f54-4117-a61b-0e09f1422b1b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-05 15:28:52,229 [info] Storing function: {'name': 'speech-diarization-diarize', 'uid': 'ec6cd014e4674966b30303ea14048acf', 'db': 'http://mlrun-api:8080'}\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
    \n", + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    diarization-test0Dec 05 15:28:52completedspeech-diarization-diarize
    v3io_user=zeevr
    kind=local
    owner=zeevr
    host=jupyter-zeev-gpu-5995df47dc-rtpvr
    data_path
    device=cpu
    speakers_labels=['Agent', 'Client']
    separate_by_channels=True
    speech-diarization
    diarize-errors
    \n", + "
    \n", + "
    \n", + "
    \n", + " Title\n", + " ×\n", + "
    \n", + " \n", + "
    \n", + "
    \n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-05 15:28:53,350 [info] Run execution finished: {'status': 'completed', 'name': 'speech-diarization-diarize'}\n" + ] + } + ], + "source": [ + "# Run the imported function with desired file/s and params\n", + "diarize_run = speech_diarization.run(\n", + " handler=\"diarize\",\n", + " inputs={\"data_path\": audio_files},\n", + " params={\n", + " \"device\": device,\n", + " \"speakers_labels\": speakers_labels,\n", + " \"separate_by_channels\": separate_by_channels,\n", + " },\n", + " returns=[\"speech-diarization: file\", \"diarize-errors: file\"],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ede77975-8843-424f-b521-b9dd56ddad28", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.py b/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.py new file mode 100644 index 00000000..6271da6a --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/src/pyannote_audio.py @@ -0,0 +1,376 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import heapq +import logging +import operator +import os +import pathlib +from functools import reduce, wraps +from typing import Any, Dict, List, Tuple, Union + +import pandas as pd +import pyannote.audio +import pyannote.core +import torch +import torchaudio +from tqdm import tqdm + +# Get the global logger: +_LOGGER = logging.getLogger() + + +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]: + is_mpi = False + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + is_mpi = context.labels.get("kind", "job") == "mpijob" + + if is_mpi: + try: + from mpi4py import MPI + + return context, MPI.COMM_WORLD + except ModuleNotFoundError as mpi4py_not_found: + context.logger.error( + "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your " + "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi." + ) + raise mpi4py_not_found + else: + return context, None + except ModuleNotFoundError as module_not_found: + if is_mpi: + raise module_not_found + return None, None + + +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_audio_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + diarization_dictionary = reduce( + operator.ior, [dia for dia, _ in output], {} + ) + errors_dictionary = reduce(operator.ior, [err for _, err in output], {}) + return diarization_dictionary, errors_dictionary + return None + + return wrapper + + return decorator + + +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def diarize( + data_path: Union[str, List[str]], + model_name: str = "pyannote/speaker-diarization-3.0", + access_token: str = None, + device: str = None, + speakers_labels: List[str] = None, + speaker_prefix: str = "speaker_", + separate_by_channels: bool = False, + minimum_speakers: int = None, + maximum_speakers: int = None, + verbose: bool = False, +) -> Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]: + """ + Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio). + The end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list + of tuples: (start, end, speaker_label). + + To use the `pyannote.audio` models you must pass a Huggingface token and get access to the required models. The + token can be passed in one of the following options: + + * Use the parameter `access_token`. + * Set an environment variable named "HUGGING_FACE_HUB_TOKEN". + * If using MLRun, you can pass it as a secret named "HUGGING_FACE_HUB_TOKEN". + + To get access to the models on Huggingface, visit their page. For example, to use the default diarization model set + in this function ("pyannote/speaker-diarization-3.0"), you need access for these two models: + + * https://huggingface.co/pyannote/segmentation-3.0 + * https://huggingface.co/pyannote/speaker-diarization-3.0 + + Note: To control the recognized speakers in the diarization output you can choose one of the following methods: + + * For a known speakers amount, you may set speaker labels via the `speakers_labels` parameter that will be used in + the order of speaking in the audio (first person speaking be the first label in the list). In addition, you can do + diarization per channel (setting the parameter `separate_by_channels` to True). Each label will be assigned to a + specific channel by order (first label to channel 0, second label to channel 1 and so on). Notice, this will + increase runtime. + * For unknown speakers amount, you can set the `speaker_prefix` parameter to add a prefix for each speaker number. + You can also help the diarization by setting the speakers range via the `speakers_amount_range` parameter. + + :param data_path: A directory of the audio files, a single file or a list of files to transcribe. + :param model_name: One of the official diarization model names (referred as diarization pipelines) of + `pyannote.audio` Huggingface page. Default: "pyannote/speaker-diarization-3.0". + :param access_token: An access token to pass for using the `pyannote.audio` models. If not provided, it + will be looking for the environment variable "HUGGING_FACE_HUB_TOKEN". If MLRun is + available, it will look for a secret "HUGGING_FACE_HUB_TOKEN". + :param device: Device to load the model. Can be one of {"cuda", "cpu"}. Default will prefer "cuda" if + available. + :param speakers_labels: Labels to use for the recognized speakers. Default: numeric labels (0, 1, ...). + :param separate_by_channels: If each speaker is speaking in a separate channel, you can diarize each channel and + combine the result into a single diarization. Each label set in the `speakers_labels` + parameter will be assigned to a specific channel by order. + :param speaker_prefix: A prefix to add for the speakers labels. This parameter is ignored if + `speakers_labels` is not None. Default: "speaker". + :param minimum_speakers: Set the minimum expected amount of speakers to be in the audio files. This parameter is + ignored if `speakers_labels` is not None. + :param maximum_speakers: Set the maximum expected amount of speakers to be in the audio files. This parameter is + ignored if `speakers_labels` is not None. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + :returns: A tuple of: + + * Speech diarization dictionary. + * A dictionary of errored files that were not transcribed. + """ + global _LOGGER + + # Get the input audio files to diarize: + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + audio_files = _get_audio_files(data_path=data_path) + else: # Should be a list of files. + audio_files = data_path + + # Get the Huggingface access token: + access_token = _get_access_token(parameter=access_token) + if access_token is None: + raise ValueError( + "A Huggingface access token must be provided to use `pyannote.audio` models. Access token can be passed " + "via one of the following options:\n" + "* Use the parameter `access_token`.\n" + "* Set an environment variable named 'HUGGING_FACE_HUB_TOKEN'.\n" + "* If using MLRun, you can pass it as a secret named 'HUGGING_FACE_HUB_TOKEN'." + ) + + # Load the diarization pipeline: + pipeline = pyannote.audio.Pipeline.from_pretrained( + checkpoint_path=model_name, use_auth_token=access_token + ) + + # Set the device: + device = device or ("cuda" if torch.cuda.is_available() else "cpu") + if device != "cpu": + pipeline.to(torch.device(device)) + + # Prepare the successes dataframe and errors dictionary to be returned: + diarizations = {} + errors = {} + + # Prepare the diarization keyword arguments: + diarize_kwargs = {} + if speakers_labels: + diarize_kwargs["num_speakers"] = len(speakers_labels) + else: + if minimum_speakers: + diarize_kwargs["min_speakers"] = minimum_speakers + if maximum_speakers: + diarize_kwargs["max_speakers"] = maximum_speakers + + # Go over the audio files and diarize: + for audio_file in tqdm( + audio_files, desc="Diarizing", unit="file", disable=not verbose + ): + try: + # Load audio file: + audio, sample_rate = torchaudio.load(uri=audio_file, channels_first=True) + # Get the diarization (if provided): + diarizations[audio_file.name] = _diarize( + audio=audio, + sample_rate=sample_rate, + pipeline=pipeline, + speakers_labels=speakers_labels, + separate_by_channels=separate_by_channels, + speaker_prefix=speaker_prefix, + diarize_kwargs=diarize_kwargs, + ) + except Exception as exception: + # Note the exception as error in the dictionary: + if verbose: + _LOGGER.warning(f"Error in file: '{audio_file.name}'") + errors[str(audio_file.name)] = str(exception) + continue + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info(f"Done ({len(diarizations)}/{len(audio_files)})\n") + return diarizations, errors + + +def _get_audio_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return audio_files + + +def _get_access_token(parameter: str) -> str: + # If given as a parameter, return it: + if parameter: + return parameter + + # Otherwise, look at the environment variable: + environment_variable = os.environ.get("HUGGING_FACE_HUB_TOKEN") + if environment_variable: + return environment_variable + + # Lastly, try look in the set secrets in MLRun: + secret = None + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + secret = context.get_secret(key="HUGGING_FACE_HUB_TOKEN") + except ModuleNotFoundError: + pass + + return secret + + +def _diarize( + audio: torch.Tensor, + sample_rate: int, + pipeline: pyannote.audio.Pipeline, + speakers_labels: List[str], + separate_by_channels: bool, + speaker_prefix: str, + diarize_kwargs: dict, +) -> List[Tuple[float, float, str]]: + # If there is no need for separation by channels, we diarize and return: + if not separate_by_channels: + # Diarize: + diarization: pyannote.core.Annotation = pipeline( + file={"waveform": audio, "sample_rate": sample_rate}, **diarize_kwargs + ) + # Verify speakers labels (should not fail here as we set `num_speakers=len(speakers_labels)` when inferring + # through the pipeline): + if speakers_labels: + given_speakers = len(speakers_labels) + found_speakers = len(set(diarization.labels())) + if given_speakers < found_speakers: + raise ValueError( + f"Not enough `speakers_labels` were given. Got {given_speakers} labels but the diarization " + f"recognized {found_speakers} speakers." + ) + # Return as a diarization list - a sorted list of tuples of start time, end time and a label (the default label + # returned is "SPEAKER_i" so we take only the index out of it): + return [ + ( + segment.start, + segment.end, + speakers_labels[int(label.split("_")[1])] + if speakers_labels + else f"{speaker_prefix}{int(label.split('_')[1])}", + ) + for segment, track, label in diarization.itertracks(yield_label=True) + ] + + # Separate to channels and diarize (we expect only one speaker per channel): + channel_diarizations = [ + _diarize( + audio=audio[channel].unsqueeze( + 0 + ), # Take channel and add a channel dimension to it. + sample_rate=sample_rate, + pipeline=pipeline, + speakers_labels=[ + speakers_labels[channel] + ], # Take the channel's label only. + separate_by_channels=False, + speaker_prefix=speaker_prefix, + diarize_kwargs={"num_speakers": 1}, # Set to one speaker. + ) + for channel in range(audio.shape[0]) + ] + + # Merge the channel diarizations into a single sorted list: + return list(heapq.merge(*channel_diarizations)) diff --git a/functions/master/pyannote_audio/1.3.0/src/test_pyannote_audio.py b/functions/master/pyannote_audio/1.3.0/src/test_pyannote_audio.py new file mode 100644 index 00000000..93da5083 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/src/test_pyannote_audio.py @@ -0,0 +1,25 @@ +import os + +import mlrun +import pytest + + +@pytest.mark.skipif("HUGGING_FACE_HUB_TOKEN" not in os.environ, reason="no token") +def test_speech_diarization(): + project = mlrun.new_project("diarization-test2") + speech_diarization = project.set_function( + func="./function.yaml", name="speech_diarization", image="mlrun/mlrun" + ) + + diarize_run = speech_diarization.run( + handler="diarize", + inputs={"data_path": os.path.join("assets", "test_data.wav")}, + params={ + "device": "cpu", + "speakers_labels": ["Agent", "Client"], + "separate_by_channels": True, + }, + returns=["speech_diarization: file", "diarize_errors: file"], + local=True, + ) + assert diarize_run.outputs["speech_diarization"] diff --git a/functions/master/pyannote_audio/1.3.0/static/documentation.html b/functions/master/pyannote_audio/1.3.0/static/documentation.html new file mode 100644 index 00000000..e28a243b --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/documentation.html @@ -0,0 +1,305 @@ + + + + + + + +pyannote_audio package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    pyannote_audio package

    + +
    + +
    +
    + +
    +
    +

    pyannote_audio package#

    +
    +

    Submodules#

    +
    +
    +

    pyannote_audio.pyannote_audio module#

    +
    +
    +pyannote_audio.pyannote_audio.diarize(data_path: str | List[str], model_name: str = 'pyannote/speaker-diarization-3.0', access_token: str | None = None, device: str | None = None, speakers_labels: List[str] | None = None, speaker_prefix: str = 'speaker_', separate_by_channels: bool = False, minimum_speakers: int | None = None, maximum_speakers: int | None = None, verbose: bool = False) Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]][source]#
    +

    Perform speech diarization on given audio files using pyannote-audio (pyannote/pyannote-audio). +The end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list +of tuples: (start, end, speaker_label).

    +

    To use the pyannote.audio models you must pass a Huggingface token and get access to the required models. The +token can be passed in one of the following options:

    +
      +
    • Use the parameter access_token.

    • +
    • Set an environment variable named “HUGGING_FACE_HUB_TOKEN”.

    • +
    • If using MLRun, you can pass it as a secret named “HUGGING_FACE_HUB_TOKEN”.

    • +
    +

    To get access to the models on Huggingface, visit their page. For example, to use the default diarization model set +in this function (“pyannote/speaker-diarization-3.0”), you need access for these two models:

    + +

    Note: To control the recognized speakers in the diarization output you can choose one of the following methods:

    +
      +
    • For a known speakers amount, you may set speaker labels via the speakers_labels parameter that will be used in +the order of speaking in the audio (first person speaking be the first label in the list). In addition, you can do +diarization per channel (setting the parameter separate_by_channels to True). Each label will be assigned to a +specific channel by order (first label to channel 0, second label to channel 1 and so on). Notice, this will +increase runtime.

    • +
    • For unknown speakers amount, you can set the speaker_prefix parameter to add a prefix for each speaker number. +You can also help the diarization by setting the speakers range via the speakers_amount_range parameter.

    • +
    +
    +
    Parameters:
    +
      +
    • data_path – A directory of the audio files, a single file or a list of files to transcribe.

    • +
    • model_name – One of the official diarization model names (referred as diarization pipelines) of +pyannote.audio Huggingface page. Default: “pyannote/speaker-diarization-3.0”.

    • +
    • access_token – An access token to pass for using the pyannote.audio models. If not provided, it +will be looking for the environment variable “HUGGING_FACE_HUB_TOKEN”. If MLRun is +available, it will look for a secret “HUGGING_FACE_HUB_TOKEN”.

    • +
    • device – Device to load the model. Can be one of {“cuda”, “cpu”}. Default will prefer “cuda” if +available.

    • +
    • speakers_labels – Labels to use for the recognized speakers. Default: numeric labels (0, 1, …).

    • +
    • separate_by_channels – If each speaker is speaking in a separate channel, you can diarize each channel and +combine the result into a single diarization. Each label set in the speakers_labels +parameter will be assigned to a specific channel by order.

    • +
    • speaker_prefix – A prefix to add for the speakers labels. This parameter is ignored if +speakers_labels is not None. Default: “speaker”.

    • +
    • minimum_speakers – Set the minimum expected amount of speakers to be in the audio files. This parameter is +ignored if speakers_labels is not None.

    • +
    • maximum_speakers – Set the maximum expected amount of speakers to be in the audio files. This parameter is +ignored if speakers_labels is not None.

    • +
    • verbose – Whether to present logs of a progress bar and errors. Default: True.

    • +
    +
    +
    Returns:
    +

    A tuple of:

    +
      +
    • Speech diarization dictionary.

    • +
    • A dictionary of errored files that were not transcribed.

    • +
    +

    +
    +
    +
    +
    +
    +pyannote_audio.pyannote_audio.open_mpi_handler(worker_inputs: List[str], root_worker_inputs: Dict[str, Any] | None = None)[source]#
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/1.3.0/static/example.html b/functions/master/pyannote_audio/1.3.0/static/example.html new file mode 100644 index 00000000..fd6ba740 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/example.html @@ -0,0 +1,470 @@ + + + + + + + +Speech diarization example notebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +

    Speech diarization example notebook

    + +
    +
    +
    +
    +
    + +
    +
    +

    Speech diarization example notebook#

    +

    In this notebook we will utilize a call diarization capability to get per-speaker speech durations from a call recording.
    +This can be useful for quantifying participation rates in calls for things like customer service analysis.

    +

    We will demonstrate this by:

    +
      +
    1. Loading in a sample call recording between multiple participants

    2. +
    3. Using a diarize() function to automatically detect speakers and estimate per-speaker talk time

    4. +
    5. Return a dictionary of described results, and a df of errors

    6. +
    +
    +
    +
    import os
    +import mlrun
    +
    +
    +
    +
    +
    +
    +
    # To use the `pyannote.audio` models you must pass a Huggingface token and get access to the required models. The
    +#    token can be passed in one of the following options:
    +#
    +#    * Use the parameter `access_token`.
    +#    * Set an environment variable named "HUGGING_FACE_HUB_TOKEN".
    +#    * If using MLRun, you can pass it as a secret named "HUGGING_FACE_HUB_TOKEN".
    +os.environ["HUGGING_FACE_HUB_TOKEN"] = <"add your token here">
    +
    +
    +
    +
    +
    +
    +
    # Create an mlrun project
    +project = mlrun.get_or_create_project("diarization-test")
    +
    +# Import the function from the yaml file, once it's in the the we can import from there 
    +speech_diarization = project.set_function(func="hub://speech_diarization", name="speech_diarization")
    +
    +
    +
    +
    +
    > 2023-12-05 15:28:51,758 [info] Project loaded successfully: {'project_name': 'diarization-test'}
    +
    +
    +
    +
    +
    +
    +
    # Set the desired run params and files
    +audio_files = os.path.join("test_data.wav")
    +device = "cpu"
    +speakers_labels = ["Agent", "Client"]
    +separate_by_channels = True
    +
    +
    +
    +
    +
    +
    +
    # Run the imported function with desired file/s and params
    +diarize_run = speech_diarization.run(
    +    handler="diarize",
    +    inputs={"data_path": audio_files},
    +    params={
    +        "device": device,
    +        "speakers_labels": speakers_labels,
    +        "separate_by_channels": separate_by_channels,
    +    },
    +    returns=["speech-diarization: file", "diarize-errors: file"],
    +    local=True,
    +)
    +
    +
    +
    +
    +
    > 2023-12-05 15:28:52,229 [info] Storing function: {'name': 'speech-diarization-diarize', 'uid': 'ec6cd014e4674966b30303ea14048acf', 'db': 'http://mlrun-api:8080'}
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    diarization-test0Dec 05 15:28:52completedspeech-diarization-diarize
    v3io_user=zeevr
    kind=local
    owner=zeevr
    host=jupyter-zeev-gpu-5995df47dc-rtpvr
    data_path
    device=cpu
    speakers_labels=['Agent', 'Client']
    separate_by_channels=True
    speech-diarization
    diarize-errors
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods or click here to open in UI
    > 2023-12-05 15:28:53,350 [info] Run execution finished: {'status': 'completed', 'name': 'speech-diarization-diarize'}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/1.3.0/static/function.html b/functions/master/pyannote_audio/1.3.0/static/function.html new file mode 100644 index 00000000..36399af3 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/function.html @@ -0,0 +1,168 @@ + + + + + + + + + + + Source + + + + +
    +        
    +kind: job
    +spec:
    +  command: ''
    +  disable_auto_mount: false
    +  image: ''
    +  build:
    +    code_origin: ''
    +    requirements:
    +    - pyannote.audio
    +    - pyannote.core
    +    - torchaudio
    +    - tqdm
    +    base_image: mlrun/mlrun-gpu
    +    origin_filename: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo=
    +  default_handler: diarize
    +  entry_points:
    +    open_mpi_handler:
    +      name: open_mpi_handler
    +      has_varargs: false
    +      lineno: 61
    +      parameters:
    +      - name: worker_inputs
    +        type: List[str]
    +      - name: root_worker_inputs
    +        type: Dict[str, Any]
    +        default: null
    +      has_kwargs: false
    +      doc: ''
    +    decorator:
    +      name: decorator
    +      has_varargs: false
    +      lineno: 73
    +      parameters:
    +      - name: handler
    +      has_kwargs: false
    +      doc: ''
    +    wrapper:
    +      name: wrapper
    +      has_varargs: false
    +      lineno: 78
    +      has_kwargs: true
    +      doc: ''
    +    diarize:
    +      name: diarize
    +      has_varargs: false
    +      lineno: 139
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]
    +      parameters:
    +      - name: data_path
    +        type: Union[str, List[str]]
    +        doc: A directory of the audio files, a single file or a list of files to transcribe.
    +      - name: model_name
    +        type: str
    +        doc: 'One of the official diarization model names (referred as diarization
    +          pipelines) of `pyannote.audio` Huggingface page. Default: "pyannote/speaker-diarization-3.0".'
    +        default: pyannote/speaker-diarization-3.0
    +      - name: access_token
    +        type: str
    +        doc: An access token to pass for using the `pyannote.audio` models. If not
    +          provided, it will be looking for the environment variable "HUGGING_FACE_HUB_TOKEN".
    +          If MLRun is available, it will look for a secret "HUGGING_FACE_HUB_TOKEN".
    +        default: null
    +      - name: device
    +        type: str
    +        doc: Device to load the model. Can be one of {"cuda", "cpu"}. Default will
    +          prefer "cuda" if available.
    +        default: null
    +      - name: speakers_labels
    +        type: List[str]
    +        doc: 'Labels to use for the recognized speakers. Default: numeric labels (0,
    +          1, ...).'
    +        default: null
    +      - name: speaker_prefix
    +        type: str
    +        doc: 'A prefix to add for the speakers labels. This parameter is ignored if
    +          `speakers_labels` is not None. Default: "speaker".'
    +        default: speaker_
    +      - name: separate_by_channels
    +        type: bool
    +        doc: If each speaker is speaking in a separate channel, you can diarize each
    +          channel and combine the result into a single diarization. Each label set
    +          in the `speakers_labels` parameter will be assigned to a specific channel
    +          by order.
    +        default: false
    +      - name: minimum_speakers
    +        type: int
    +        doc: Set the minimum expected amount of speakers to be in the audio files.
    +          This parameter is ignored if `speakers_labels` is not None.
    +        default: null
    +      - name: maximum_speakers
    +        type: int
    +        doc: Set the maximum expected amount of speakers to be in the audio files.
    +          This parameter is ignored if `speakers_labels` is not None.
    +        default: null
    +      - name: verbose
    +        type: bool
    +        doc: 'Whether to present logs of a progress bar and errors. Default: True.'
    +        default: false
    +      has_kwargs: false
    +      doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\
    +        The end result is a dictionary with the file names as keys and their diarization\
    +        \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\
    +        \nTo use the `pyannote.audio` models you must pass a Huggingface token and\
    +        \ get access to the required models. The\ntoken can be passed in one of the\
    +        \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\
    +        \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\
    +        \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\
    +        \ models on Huggingface, visit their page. For example, to use the default\
    +        \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\
    +        ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\
    +        * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\
    +        \ the recognized speakers in the diarization output you can choose one of\
    +        \ the following methods:\n\n* For a known speakers amount, you may set speaker\
    +        \ labels via the `speakers_labels` parameter that will be used in\n  the order\
    +        \ of speaking in the audio (first person speaking be the first label in the\
    +        \ list). In addition, you can do\n  diarization per channel (setting the parameter\
    +        \ `separate_by_channels` to True). Each label will be assigned to a\n  specific\
    +        \ channel by order (first label to channel 0, second label to channel 1 and\
    +        \ so on). Notice, this will\n  increase runtime.\n* For unknown speakers amount,\
    +        \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\
    +        \ number.\n  You can also help the diarization by setting the speakers range\
    +        \ via the `speakers_amount_range` parameter."
    +  description: pyannote's speech diarization of audio files
    +metadata:
    +  name: pyannote-audio
    +  tag: ''
    +  categories:
    +  - deep-learning
    +  - audio
    +verbose: false
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/1.3.0/static/item.html b/functions/master/pyannote_audio/1.3.0/static/item.html new file mode 100644 index 00000000..2fbd98e6 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/item.html @@ -0,0 +1,64 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- deep-learning
    +- audio
    +description: pyannote's speech diarization of audio files
    +doc: ''
    +example: pyannote_audio.ipynb
    +generationDate: 2023-12-03:14-30
    +hidden: false
    +icon: ''
    +labels:
    +  author: guyl
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: pyannote-audio
    +platformVersion: 3.5.3
    +spec:
    +  filename: pyannote_audio.py
    +  handler: diarize
    +  image: mlrun/mlrun-gpu
    +  kind: job
    +  requirements:
    +  - pyannote.audio
    +  - pyannote.core
    +  - torchaudio
    +  - tqdm
    +url: ''
    +version: 1.3.0
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/1.3.0/static/pyannote_audio.html b/functions/master/pyannote_audio/1.3.0/static/pyannote_audio.html new file mode 100644 index 00000000..2d9f2aa3 --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/pyannote_audio.html @@ -0,0 +1,554 @@ + + + + + + + +pyannote_audio.pyannote_audio + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for pyannote_audio.pyannote_audio

    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import heapq
    +import logging
    +import operator
    +import os
    +import pathlib
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import pyannote.audio
    +import pyannote.core
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +        else:
    +            return context, None
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +
    +[docs] +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_audio_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + diarization_dictionary = reduce( + operator.ior, [dia for dia, _ in output], {} + ) + errors_dictionary = reduce(operator.ior, [err for _, err in output], {}) + return diarization_dictionary, errors_dictionary + return None + + return wrapper + + return decorator
    + + + +
    +[docs] +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def diarize( + data_path: Union[str, List[str]], + model_name: str = "pyannote/speaker-diarization-3.0", + access_token: str = None, + device: str = None, + speakers_labels: List[str] = None, + speaker_prefix: str = "speaker_", + separate_by_channels: bool = False, + minimum_speakers: int = None, + maximum_speakers: int = None, + verbose: bool = False, +) -> Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]: + """ + Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio). + The end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list + of tuples: (start, end, speaker_label). + + To use the `pyannote.audio` models you must pass a Huggingface token and get access to the required models. The + token can be passed in one of the following options: + + * Use the parameter `access_token`. + * Set an environment variable named "HUGGING_FACE_HUB_TOKEN". + * If using MLRun, you can pass it as a secret named "HUGGING_FACE_HUB_TOKEN". + + To get access to the models on Huggingface, visit their page. For example, to use the default diarization model set + in this function ("pyannote/speaker-diarization-3.0"), you need access for these two models: + + * https://huggingface.co/pyannote/segmentation-3.0 + * https://huggingface.co/pyannote/speaker-diarization-3.0 + + Note: To control the recognized speakers in the diarization output you can choose one of the following methods: + + * For a known speakers amount, you may set speaker labels via the `speakers_labels` parameter that will be used in + the order of speaking in the audio (first person speaking be the first label in the list). In addition, you can do + diarization per channel (setting the parameter `separate_by_channels` to True). Each label will be assigned to a + specific channel by order (first label to channel 0, second label to channel 1 and so on). Notice, this will + increase runtime. + * For unknown speakers amount, you can set the `speaker_prefix` parameter to add a prefix for each speaker number. + You can also help the diarization by setting the speakers range via the `speakers_amount_range` parameter. + + :param data_path: A directory of the audio files, a single file or a list of files to transcribe. + :param model_name: One of the official diarization model names (referred as diarization pipelines) of + `pyannote.audio` Huggingface page. Default: "pyannote/speaker-diarization-3.0". + :param access_token: An access token to pass for using the `pyannote.audio` models. If not provided, it + will be looking for the environment variable "HUGGING_FACE_HUB_TOKEN". If MLRun is + available, it will look for a secret "HUGGING_FACE_HUB_TOKEN". + :param device: Device to load the model. Can be one of {"cuda", "cpu"}. Default will prefer "cuda" if + available. + :param speakers_labels: Labels to use for the recognized speakers. Default: numeric labels (0, 1, ...). + :param separate_by_channels: If each speaker is speaking in a separate channel, you can diarize each channel and + combine the result into a single diarization. Each label set in the `speakers_labels` + parameter will be assigned to a specific channel by order. + :param speaker_prefix: A prefix to add for the speakers labels. This parameter is ignored if + `speakers_labels` is not None. Default: "speaker". + :param minimum_speakers: Set the minimum expected amount of speakers to be in the audio files. This parameter is + ignored if `speakers_labels` is not None. + :param maximum_speakers: Set the maximum expected amount of speakers to be in the audio files. This parameter is + ignored if `speakers_labels` is not None. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + :returns: A tuple of: + + * Speech diarization dictionary. + * A dictionary of errored files that were not transcribed. + """ + global _LOGGER + + # Get the input audio files to diarize: + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + audio_files = _get_audio_files(data_path=data_path) + else: # Should be a list of files. + audio_files = data_path + + # Get the Huggingface access token: + access_token = _get_access_token(parameter=access_token) + if access_token is None: + raise ValueError( + "A Huggingface access token must be provided to use `pyannote.audio` models. Access token can be passed " + "via one of the following options:\n" + "* Use the parameter `access_token`.\n" + "* Set an environment variable named 'HUGGING_FACE_HUB_TOKEN'.\n" + "* If using MLRun, you can pass it as a secret named 'HUGGING_FACE_HUB_TOKEN'." + ) + + # Load the diarization pipeline: + pipeline = pyannote.audio.Pipeline.from_pretrained( + checkpoint_path=model_name, use_auth_token=access_token + ) + + # Set the device: + device = device or ("cuda" if torch.cuda.is_available() else "cpu") + if device != "cpu": + pipeline.to(torch.device(device)) + + # Prepare the successes dataframe and errors dictionary to be returned: + diarizations = {} + errors = {} + + # Prepare the diarization keyword arguments: + diarize_kwargs = {} + if speakers_labels: + diarize_kwargs["num_speakers"] = len(speakers_labels) + else: + if minimum_speakers: + diarize_kwargs["min_speakers"] = minimum_speakers + if maximum_speakers: + diarize_kwargs["max_speakers"] = maximum_speakers + + # Go over the audio files and diarize: + for audio_file in tqdm( + audio_files, desc="Diarizing", unit="file", disable=not verbose + ): + try: + # Load audio file: + audio, sample_rate = torchaudio.load(uri=audio_file, channels_first=True) + # Get the diarization (if provided): + diarizations[audio_file.name] = _diarize( + audio=audio, + sample_rate=sample_rate, + pipeline=pipeline, + speakers_labels=speakers_labels, + separate_by_channels=separate_by_channels, + speaker_prefix=speaker_prefix, + diarize_kwargs=diarize_kwargs, + ) + except Exception as exception: + # Note the exception as error in the dictionary: + if verbose: + _LOGGER.warning(f"Error in file: '{audio_file.name}'") + errors[str(audio_file.name)] = str(exception) + continue + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info(f"Done ({len(diarizations)}/{len(audio_files)})\n") + return diarizations, errors
    + + + +def _get_audio_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return audio_files + + +def _get_access_token(parameter: str) -> str: + # If given as a parameter, return it: + if parameter: + return parameter + + # Otherwise, look at the environment variable: + environment_variable = os.environ.get("HUGGING_FACE_HUB_TOKEN") + if environment_variable: + return environment_variable + + # Lastly, try look in the set secrets in MLRun: + secret = None + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + secret = context.get_secret(key="HUGGING_FACE_HUB_TOKEN") + except ModuleNotFoundError: + pass + + return secret + + +def _diarize( + audio: torch.Tensor, + sample_rate: int, + pipeline: pyannote.audio.Pipeline, + speakers_labels: List[str], + separate_by_channels: bool, + speaker_prefix: str, + diarize_kwargs: dict, +) -> List[Tuple[float, float, str]]: + # If there is no need for separation by channels, we diarize and return: + if not separate_by_channels: + # Diarize: + diarization: pyannote.core.Annotation = pipeline( + file={"waveform": audio, "sample_rate": sample_rate}, **diarize_kwargs + ) + # Verify speakers labels (should not fail here as we set `num_speakers=len(speakers_labels)` when inferring + # through the pipeline): + if speakers_labels: + given_speakers = len(speakers_labels) + found_speakers = len(set(diarization.labels())) + if given_speakers < found_speakers: + raise ValueError( + f"Not enough `speakers_labels` were given. Got {given_speakers} labels but the diarization " + f"recognized {found_speakers} speakers." + ) + # Return as a diarization list - a sorted list of tuples of start time, end time and a label (the default label + # returned is "SPEAKER_i" so we take only the index out of it): + return [ + ( + segment.start, + segment.end, + speakers_labels[int(label.split("_")[1])] + if speakers_labels + else f"{speaker_prefix}{int(label.split('_')[1])}", + ) + for segment, track, label in diarization.itertracks(yield_label=True) + ] + + # Separate to channels and diarize (we expect only one speaker per channel): + channel_diarizations = [ + _diarize( + audio=audio[channel].unsqueeze( + 0 + ), # Take channel and add a channel dimension to it. + sample_rate=sample_rate, + pipeline=pipeline, + speakers_labels=[ + speakers_labels[channel] + ], # Take the channel's label only. + separate_by_channels=False, + speaker_prefix=speaker_prefix, + diarize_kwargs={"num_speakers": 1}, # Set to one speaker. + ) + for channel in range(audio.shape[0]) + ] + + # Merge the channel diarizations into a single sorted list: + return list(heapq.merge(*channel_diarizations)) +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/1.3.0/static/source.html b/functions/master/pyannote_audio/1.3.0/static/source.html new file mode 100644 index 00000000..f3f0617e --- /dev/null +++ b/functions/master/pyannote_audio/1.3.0/static/source.html @@ -0,0 +1,411 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import heapq
    +import logging
    +import operator
    +import os
    +import pathlib
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import pyannote.audio
    +import pyannote.core
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +        else:
    +            return context, None
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +def open_mpi_handler(
    +    worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None
    +):
    +    global _LOGGER
    +
    +    # Check for MLRun and OpenMPI availability:
    +    context, comm = _check_mlrun_and_open_mpi()
    +
    +    # Check if MLRun is available, set the global logger to MLRun's:
    +    if context:
    +        _LOGGER = context.logger
    +
    +    def decorator(handler):
    +        if comm is None or comm.Get_size() == 1:
    +            return handler
    +
    +        @wraps(handler)
    +        def wrapper(**kwargs):
    +            # Get the open mpi environment properties:
    +            size = comm.Get_size()
    +            rank = comm.Get_rank()
    +
    +            # Give the correct chunk of the workers inputs:
    +            for worker_input in worker_inputs:
    +                input_argument = kwargs[worker_input]
    +                if input_argument is None:
    +                    continue
    +                if isinstance(input_argument, str):
    +                    input_argument = _get_audio_files(
    +                        data_path=pathlib.Path(input_argument).absolute()
    +                    )
    +                if len(input_argument) < size:
    +                    raise ValueError(
    +                        f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. "
    +                        f"Please reduce the amount of workers for this input."
    +                    )
    +                even_chunk_size = len(input_argument) // size
    +                chunk_start = rank * even_chunk_size
    +                chunk_end = (
    +                    (rank + 1) * even_chunk_size
    +                    if rank + 1 < size
    +                    else len(input_argument)
    +                )
    +                context.logger.info(
    +                    f"Rank #{rank}: Processing input chunk of '{worker_input}' "
    +                    f"from index {chunk_start} to {chunk_end}."
    +                )
    +                if isinstance(input_argument, list):
    +                    input_argument = input_argument[chunk_start:chunk_end]
    +                elif isinstance(input_argument, pd.DataFrame):
    +                    input_argument = input_argument.iloc[chunk_start:chunk_end:, :]
    +                kwargs[worker_input] = input_argument
    +
    +            # Set the root worker only arguments:
    +            if rank == 0 and root_worker_inputs:
    +                kwargs.update(root_worker_inputs)
    +
    +            # Run the worker:
    +            output = handler(**kwargs)
    +
    +            # Send the output to the root rank (rank #0):
    +            output = comm.gather(output, root=0)
    +            if rank == 0:
    +                # Join the outputs:
    +                context.logger.info("Collecting data from workers to root worker.")
    +                diarization_dictionary = reduce(
    +                    operator.ior, [dia for dia, _ in output], {}
    +                )
    +                errors_dictionary = reduce(operator.ior, [err for _, err in output], {})
    +                return diarization_dictionary, errors_dictionary
    +            return None
    +
    +        return wrapper
    +
    +    return decorator
    +
    +
    +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True})
    +def diarize(
    +    data_path: Union[str, List[str]],
    +    model_name: str = "pyannote/speaker-diarization-3.0",
    +    access_token: str = None,
    +    device: str = None,
    +    speakers_labels: List[str] = None,
    +    speaker_prefix: str = "speaker_",
    +    separate_by_channels: bool = False,
    +    minimum_speakers: int = None,
    +    maximum_speakers: int = None,
    +    verbose: bool = False,
    +) -> Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]:
    +    """
    +    Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).
    +    The end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list
    +    of tuples: (start, end, speaker_label).
    +
    +    To use the `pyannote.audio` models you must pass a Huggingface token and get access to the required models. The
    +    token can be passed in one of the following options:
    +
    +    * Use the parameter `access_token`.
    +    * Set an environment variable named "HUGGING_FACE_HUB_TOKEN".
    +    * If using MLRun, you can pass it as a secret named "HUGGING_FACE_HUB_TOKEN".
    +
    +    To get access to the models on Huggingface, visit their page. For example, to use the default diarization model set
    +    in this function ("pyannote/speaker-diarization-3.0"), you need access for these two models:
    +
    +    * https://huggingface.co/pyannote/segmentation-3.0
    +    * https://huggingface.co/pyannote/speaker-diarization-3.0
    +
    +    Note: To control the recognized speakers in the diarization output you can choose one of the following methods:
    +
    +    * For a known speakers amount, you may set speaker labels via the `speakers_labels` parameter that will be used in
    +      the order of speaking in the audio (first person speaking be the first label in the list). In addition, you can do
    +      diarization per channel (setting the parameter `separate_by_channels` to True). Each label will be assigned to a
    +      specific channel by order (first label to channel 0, second label to channel 1 and so on). Notice, this will
    +      increase runtime.
    +    * For unknown speakers amount, you can set the `speaker_prefix` parameter to add a prefix for each speaker number.
    +      You can also help the diarization by setting the speakers range via the `speakers_amount_range` parameter.
    +
    +    :param data_path:            A directory of the audio files, a single file or a list of files to transcribe.
    +    :param model_name:           One of the official diarization model names (referred as diarization pipelines) of
    +                                 `pyannote.audio` Huggingface page. Default: "pyannote/speaker-diarization-3.0".
    +    :param access_token:         An access token to pass for using the `pyannote.audio` models. If not provided, it
    +                                 will be looking for the environment variable "HUGGING_FACE_HUB_TOKEN". If MLRun is
    +                                 available, it will look for a secret "HUGGING_FACE_HUB_TOKEN".
    +    :param device:               Device to load the model. Can be one of {"cuda", "cpu"}. Default will prefer "cuda" if
    +                                 available.
    +    :param speakers_labels:      Labels to use for the recognized speakers. Default: numeric labels (0, 1, ...).
    +    :param separate_by_channels: If each speaker is speaking in a separate channel, you can diarize each channel and
    +                                 combine the result into a single diarization. Each label set in the `speakers_labels`
    +                                 parameter will be assigned to a specific channel by order.
    +    :param speaker_prefix:       A prefix to add for the speakers labels. This parameter is ignored if
    +                                 `speakers_labels` is not None. Default: "speaker".
    +    :param minimum_speakers:     Set the minimum expected amount of speakers to be in the audio files. This parameter is
    +                                 ignored if `speakers_labels` is not None.
    +    :param maximum_speakers:     Set the maximum expected amount of speakers to be in the audio files. This parameter is
    +                                 ignored if `speakers_labels` is not None.
    +    :param verbose:              Whether to present logs of a progress bar and errors. Default: True.
    +
    +    :returns: A tuple of:
    +
    +              * Speech diarization dictionary.
    +              * A dictionary of errored files that were not transcribed.
    +    """
    +    global _LOGGER
    +
    +    # Get the input audio files to diarize:
    +    if isinstance(data_path, str):
    +        data_path = pathlib.Path(data_path).absolute()
    +        audio_files = _get_audio_files(data_path=data_path)
    +    else:  # Should be a list of files.
    +        audio_files = data_path
    +
    +    # Get the Huggingface access token:
    +    access_token = _get_access_token(parameter=access_token)
    +    if access_token is None:
    +        raise ValueError(
    +            "A Huggingface access token must be provided to use `pyannote.audio` models. Access token can be passed "
    +            "via one of the following options:\n"
    +            "* Use the parameter `access_token`.\n"
    +            "* Set an environment variable named 'HUGGING_FACE_HUB_TOKEN'.\n"
    +            "* If using MLRun, you can pass it as a secret named 'HUGGING_FACE_HUB_TOKEN'."
    +        )
    +
    +    # Load the diarization pipeline:
    +    pipeline = pyannote.audio.Pipeline.from_pretrained(
    +        checkpoint_path=model_name, use_auth_token=access_token
    +    )
    +
    +    # Set the device:
    +    device = device or ("cuda" if torch.cuda.is_available() else "cpu")
    +    if device != "cpu":
    +        pipeline.to(torch.device(device))
    +
    +    # Prepare the successes dataframe and errors dictionary to be returned:
    +    diarizations = {}
    +    errors = {}
    +
    +    # Prepare the diarization keyword arguments:
    +    diarize_kwargs = {}
    +    if speakers_labels:
    +        diarize_kwargs["num_speakers"] = len(speakers_labels)
    +    else:
    +        if minimum_speakers:
    +            diarize_kwargs["min_speakers"] = minimum_speakers
    +        if maximum_speakers:
    +            diarize_kwargs["max_speakers"] = maximum_speakers
    +
    +    # Go over the audio files and diarize:
    +    for audio_file in tqdm(
    +        audio_files, desc="Diarizing", unit="file", disable=not verbose
    +    ):
    +        try:
    +            # Load audio file:
    +            audio, sample_rate = torchaudio.load(uri=audio_file, channels_first=True)
    +            # Get the diarization (if provided):
    +            diarizations[audio_file.name] = _diarize(
    +                audio=audio,
    +                sample_rate=sample_rate,
    +                pipeline=pipeline,
    +                speakers_labels=speakers_labels,
    +                separate_by_channels=separate_by_channels,
    +                speaker_prefix=speaker_prefix,
    +                diarize_kwargs=diarize_kwargs,
    +            )
    +        except Exception as exception:
    +            # Note the exception as error in the dictionary:
    +            if verbose:
    +                _LOGGER.warning(f"Error in file: '{audio_file.name}'")
    +            errors[str(audio_file.name)] = str(exception)
    +            continue
    +
    +    # Print the head of the produced dataframe and return:
    +    if verbose:
    +        _LOGGER.info(f"Done ({len(diarizations)}/{len(audio_files)})\n")
    +    return diarizations, errors
    +
    +
    +def _get_audio_files(
    +    data_path: pathlib.Path,
    +) -> List[pathlib.Path]:
    +    # Check if the path is of a directory or a file:
    +    if data_path.is_dir():
    +        # Get all files inside the directory:
    +        audio_files = list(data_path.glob("*.*"))
    +    elif data_path.is_file():
    +        audio_files = [data_path]
    +    else:
    +        raise ValueError(
    +            f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. "
    +            f"Given: {str(data_path)} "
    +        )
    +
    +    return audio_files
    +
    +
    +def _get_access_token(parameter: str) -> str:
    +    # If given as a parameter, return it:
    +    if parameter:
    +        return parameter
    +
    +    # Otherwise, look at the environment variable:
    +    environment_variable = os.environ.get("HUGGING_FACE_HUB_TOKEN")
    +    if environment_variable:
    +        return environment_variable
    +
    +    # Lastly, try look in the set secrets in MLRun:
    +    secret = None
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        secret = context.get_secret(key="HUGGING_FACE_HUB_TOKEN")
    +    except ModuleNotFoundError:
    +        pass
    +
    +    return secret
    +
    +
    +def _diarize(
    +    audio: torch.Tensor,
    +    sample_rate: int,
    +    pipeline: pyannote.audio.Pipeline,
    +    speakers_labels: List[str],
    +    separate_by_channels: bool,
    +    speaker_prefix: str,
    +    diarize_kwargs: dict,
    +) -> List[Tuple[float, float, str]]:
    +    # If there is no need for separation by channels, we diarize and return:
    +    if not separate_by_channels:
    +        # Diarize:
    +        diarization: pyannote.core.Annotation = pipeline(
    +            file={"waveform": audio, "sample_rate": sample_rate}, **diarize_kwargs
    +        )
    +        # Verify speakers labels (should not fail here as we set `num_speakers=len(speakers_labels)` when inferring
    +        # through the pipeline):
    +        if speakers_labels:
    +            given_speakers = len(speakers_labels)
    +            found_speakers = len(set(diarization.labels()))
    +            if given_speakers < found_speakers:
    +                raise ValueError(
    +                    f"Not enough `speakers_labels` were given. Got {given_speakers} labels but the diarization "
    +                    f"recognized {found_speakers} speakers."
    +                )
    +        # Return as a diarization list - a sorted list of tuples of start time, end time and a label (the default label
    +        # returned is "SPEAKER_i" so we take only the index out of it):
    +        return [
    +            (
    +                segment.start,
    +                segment.end,
    +                speakers_labels[int(label.split("_")[1])]
    +                if speakers_labels
    +                else f"{speaker_prefix}{int(label.split('_')[1])}",
    +            )
    +            for segment, track, label in diarization.itertracks(yield_label=True)
    +        ]
    +
    +    # Separate to channels and diarize (we expect only one speaker per channel):
    +    channel_diarizations = [
    +        _diarize(
    +            audio=audio[channel].unsqueeze(
    +                0
    +            ),  # Take channel and add a channel dimension to it.
    +            sample_rate=sample_rate,
    +            pipeline=pipeline,
    +            speakers_labels=[
    +                speakers_labels[channel]
    +            ],  # Take the channel's label only.
    +            separate_by_channels=False,
    +            speaker_prefix=speaker_prefix,
    +            diarize_kwargs={"num_speakers": 1},  # Set to one speaker.
    +        )
    +        for channel in range(audio.shape[0])
    +    ]
    +
    +    # Merge the channel diarizations into a single sorted list:
    +    return list(heapq.merge(*channel_diarizations))
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/pyannote_audio/latest/src/function.yaml b/functions/master/pyannote_audio/latest/src/function.yaml index 30870afa..b4cd9ad9 100644 --- a/functions/master/pyannote_audio/latest/src/function.yaml +++ b/functions/master/pyannote_audio/latest/src/function.yaml @@ -1,86 +1,53 @@ kind: job -metadata: - name: pyannote-audio - tag: '' - hash: aed670a0534ebf30690dd2af7acad35595c7d5b1 - project: '' - labels: - author: guyl - categories: - - deep-learning - - huggingface - - audio spec: command: '' - args: [] + disable_auto_mount: false image: '' build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo= - base_image: mlrun/mlrun-gpu - commands: [] code_origin: '' - origin_filename: '' requirements: - pyannote.audio - pyannote.core - torchaudio - tqdm + base_image: mlrun/mlrun-gpu + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo= + default_handler: diarize entry_points: open_mpi_handler: name: open_mpi_handler - doc: '' + has_varargs: false + lineno: 61 parameters: - name: worker_inputs type: List[str] - name: root_worker_inputs type: Dict[str, Any] default: null - outputs: [] - lineno: 61 - has_varargs: false has_kwargs: false + doc: '' decorator: name: decorator - doc: '' + has_varargs: false + lineno: 73 parameters: - name: handler - outputs: [] - lineno: 73 - has_varargs: false has_kwargs: false + doc: '' wrapper: name: wrapper - doc: '' - parameters: [] - outputs: [] - lineno: 78 has_varargs: false + lineno: 78 has_kwargs: true + doc: '' diarize: name: diarize - doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\ - The end result is a dictionary with the file names as keys and their diarization\ - \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\ - \nTo use the `pyannote.audio` models you must pass a Huggingface token and\ - \ get access to the required models. The\ntoken can be passed in one of the\ - \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\ - \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\ - \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\ - \ models on Huggingface, visit their page. For example, to use the default\ - \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\ - ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\ - * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\ - \ the recognized speakers in the diarization output you can choose one of\ - \ the following methods:\n\n* For a known speakers amount, you may set speaker\ - \ labels via the `speakers_labels` parameter that will be used in\n the order\ - \ of speaking in the audio (first person speaking be the first label in the\ - \ list). In addition, you can do\n diarization per channel (setting the parameter\ - \ `separate_by_channels` to True). Each label will be assigned to a\n specific\ - \ channel by order (first label to channel 0, second label to channel 1 and\ - \ so on). Notice, this will\n increase runtime.\n* For unknown speakers amount,\ - \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\ - \ number.\n You can also help the diarization by setting the speakers range\ - \ via the `speakers_amount_range` parameter." + has_varargs: false + lineno: 139 + outputs: + - doc: 'A tuple of:' + type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]] parameters: - name: data_path type: Union[str, List[str]] @@ -132,20 +99,35 @@ spec: type: bool doc: 'Whether to present logs of a progress bar and errors. Default: True.' default: false - outputs: - - doc: 'A tuple of:' - type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]] - lineno: 139 - has_varargs: false has_kwargs: false + doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\ + The end result is a dictionary with the file names as keys and their diarization\ + \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\ + \nTo use the `pyannote.audio` models you must pass a Huggingface token and\ + \ get access to the required models. The\ntoken can be passed in one of the\ + \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\ + \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\ + \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\ + \ models on Huggingface, visit their page. For example, to use the default\ + \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\ + ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\ + * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\ + \ the recognized speakers in the diarization output you can choose one of\ + \ the following methods:\n\n* For a known speakers amount, you may set speaker\ + \ labels via the `speakers_labels` parameter that will be used in\n the order\ + \ of speaking in the audio (first person speaking be the first label in the\ + \ list). In addition, you can do\n diarization per channel (setting the parameter\ + \ `separate_by_channels` to True). Each label will be assigned to a\n specific\ + \ channel by order (first label to channel 0, second label to channel 1 and\ + \ so on). Notice, this will\n increase runtime.\n* For unknown speakers amount,\ + \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\ + \ number.\n You can also help the diarization by setting the speakers range\ + \ via the `speakers_amount_range` parameter." description: pyannote's speech diarization of audio files - default_handler: diarize - disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} +metadata: + name: pyannote-audio + tag: '' + categories: + - deep-learning + - audio verbose: false diff --git a/functions/master/pyannote_audio/latest/src/item.yaml b/functions/master/pyannote_audio/latest/src/item.yaml index b69add9e..b6dbccdd 100644 --- a/functions/master/pyannote_audio/latest/src/item.yaml +++ b/functions/master/pyannote_audio/latest/src/item.yaml @@ -1,7 +1,6 @@ apiVersion: v1 categories: - deep-learning -- huggingface - audio description: pyannote's speech diarization of audio files doc: '' @@ -13,7 +12,7 @@ labels: author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: pyannote-audio platformVersion: 3.5.3 spec: @@ -27,4 +26,4 @@ spec: - torchaudio - tqdm url: '' -version: 1.2.0 +version: 1.3.0 diff --git a/functions/master/pyannote_audio/latest/static/documentation.html b/functions/master/pyannote_audio/latest/static/documentation.html index 8c247d59..e28a243b 100644 --- a/functions/master/pyannote_audio/latest/static/documentation.html +++ b/functions/master/pyannote_audio/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pyannote_audio/latest/static/example.html b/functions/master/pyannote_audio/latest/static/example.html index 47ad38d5..fd6ba740 100644 --- a/functions/master/pyannote_audio/latest/static/example.html +++ b/functions/master/pyannote_audio/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/pyannote_audio/latest/static/function.html b/functions/master/pyannote_audio/latest/static/function.html index bf24893b..36399af3 100644 --- a/functions/master/pyannote_audio/latest/static/function.html +++ b/functions/master/pyannote_audio/latest/static/function.html @@ -29,88 +29,55 @@
             
     kind: job
    -metadata:
    -  name: pyannote-audio
    -  tag: ''
    -  hash: aed670a0534ebf30690dd2af7acad35595c7d5b1
    -  project: ''
    -  labels:
    -    author: guyl
    -  categories:
    -  - deep-learning
    -  - huggingface
    -  - audio
     spec:
       command: ''
    -  args: []
    +  disable_auto_mount: false
       image: ''
       build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo=
    -    base_image: mlrun/mlrun-gpu
    -    commands: []
         code_origin: ''
    -    origin_filename: ''
         requirements:
         - pyannote.audio
         - pyannote.core
         - torchaudio
         - tqdm
    +    base_image: mlrun/mlrun-gpu
    +    origin_filename: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGhlYXBxCmltcG9ydCBsb2dnaW5nCmltcG9ydCBvcGVyYXRvcgppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHJlZHVjZSwgd3JhcHMKZnJvbSB0eXBpbmcgaW1wb3J0IEFueSwgRGljdCwgTGlzdCwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCBweWFubm90ZS5hdWRpbwppbXBvcnQgcHlhbm5vdGUuY29yZQppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1wYXRobGliLlBhdGgoaW5wdXRfYXJndW1lbnQpLmFic29sdXRlKCkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBsZW4oaW5wdXRfYXJndW1lbnQpIDwgc2l6ZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgICAgICAgICBmIkNhbm5vdCBzcGxpdCB0aGUgaW5wdXQgJ3t3b3JrZXJfaW5wdXR9JyBvZiBsZW5ndGgge2xlbihpbnB1dF9hcmd1bWVudCl9IHRvIHtzaXplfSB3b3JrZXJzLiAiCiAgICAgICAgICAgICAgICAgICAgICAgIGYiUGxlYXNlIHJlZHVjZSB0aGUgYW1vdW50IG9mIHdvcmtlcnMgZm9yIHRoaXMgaW5wdXQuIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV2ZW5fY2h1bmtfc2l6ZSA9IGxlbihpbnB1dF9hcmd1bWVudCkgLy8gc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfc3RhcnQgPSByYW5rICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICBjaHVua19lbmQgPSAoCiAgICAgICAgICAgICAgICAgICAgKHJhbmsgKyAxKSAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgICAgIGlmIHJhbmsgKyAxIDwgc2l6ZQogICAgICAgICAgICAgICAgICAgIGVsc2UgbGVuKGlucHV0X2FyZ3VtZW50KQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygKICAgICAgICAgICAgICAgICAgICBmIlJhbmsgI3tyYW5rfTogUHJvY2Vzc2luZyBpbnB1dCBjaHVuayBvZiAne3dvcmtlcl9pbnB1dH0nICIKICAgICAgICAgICAgICAgICAgICBmImZyb20gaW5kZXgge2NodW5rX3N0YXJ0fSB0byB7Y2h1bmtfZW5kfS4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBsaXN0KToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50W2NodW5rX3N0YXJ0OmNodW5rX2VuZF0KICAgICAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgcGQuRGF0YUZyYW1lKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IGlucHV0X2FyZ3VtZW50Lmlsb2NbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kOiwgOl0KICAgICAgICAgICAgICAgIGt3YXJnc1t3b3JrZXJfaW5wdXRdID0gaW5wdXRfYXJndW1lbnQKCiAgICAgICAgICAgICMgU2V0IHRoZSByb290IHdvcmtlciBvbmx5IGFyZ3VtZW50czoKICAgICAgICAgICAgaWYgcmFuayA9PSAwIGFuZCByb290X3dvcmtlcl9pbnB1dHM6CiAgICAgICAgICAgICAgICBrd2FyZ3MudXBkYXRlKHJvb3Rfd29ya2VyX2lucHV0cykKCiAgICAgICAgICAgICMgUnVuIHRoZSB3b3JrZXI6CiAgICAgICAgICAgIG91dHB1dCA9IGhhbmRsZXIoKiprd2FyZ3MpCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCiAgICAgICAgICAgIGlmIHJhbmsgPT0gMDoKICAgICAgICAgICAgICAgICMgSm9pbiB0aGUgb3V0cHV0czoKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oIkNvbGxlY3RpbmcgZGF0YSBmcm9tIHdvcmtlcnMgdG8gcm9vdCB3b3JrZXIuIikKICAgICAgICAgICAgICAgIGRpYXJpemF0aW9uX2RpY3Rpb25hcnkgPSByZWR1Y2UoCiAgICAgICAgICAgICAgICAgICAgb3BlcmF0b3IuaW9yLCBbZGlhIGZvciBkaWEsIF8gaW4gb3V0cHV0XSwge30KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgZXJyIGluIG91dHB1dF0sIHt9KQogICAgICAgICAgICAgICAgcmV0dXJuIGRpYXJpemF0aW9uX2RpY3Rpb25hcnksIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgZGlhcml6ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyID0gInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIiwKICAgIGFjY2Vzc190b2tlbjogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIHNwZWFrZXJzX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgIHNwZWFrZXJfcHJlZml4OiBzdHIgPSAic3BlYWtlcl8iLAogICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM6IGJvb2wgPSBGYWxzZSwKICAgIG1pbmltdW1fc3BlYWtlcnM6IGludCA9IE5vbmUsCiAgICBtYXhpbXVtX3NwZWFrZXJzOiBpbnQgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW0RpY3Rbc3RyLCBMaXN0W1R1cGxlW2Zsb2F0LCBmbG9hdCwgc3RyXV1dLCBEaWN0W3N0ciwgc3RyXV06CiAgICAiIiIKICAgIFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIG9uIGdpdmVuIGF1ZGlvIGZpbGVzIHVzaW5nIHB5YW5ub3RlLWF1ZGlvIChodHRwczovL2dpdGh1Yi5jb20vcHlhbm5vdGUvcHlhbm5vdGUtYXVkaW8pLgogICAgVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBUbyB1c2UgdGhlIGBweWFubm90ZS5hdWRpb2AgbW9kZWxzIHlvdSBtdXN0IHBhc3MgYSBIdWdnaW5nZmFjZSB0b2tlbiBhbmQgZ2V0IGFjY2VzcyB0byB0aGUgcmVxdWlyZWQgbW9kZWxzLiBUaGUKICAgIHRva2VuIGNhbiBiZSBwYXNzZWQgaW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoKCiAgICAqIFVzZSB0aGUgcGFyYW1ldGVyIGBhY2Nlc3NfdG9rZW5gLgogICAgKiBTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgbmFtZWQgIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iLgogICAgKiBJZiB1c2luZyBNTFJ1biwgeW91IGNhbiBwYXNzIGl0IGFzIGEgc2VjcmV0IG5hbWVkICJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIi4KCiAgICBUbyBnZXQgYWNjZXNzIHRvIHRoZSBtb2RlbHMgb24gSHVnZ2luZ2ZhY2UsIHZpc2l0IHRoZWlyIHBhZ2UuIEZvciBleGFtcGxlLCB0byB1c2UgdGhlIGRlZmF1bHQgZGlhcml6YXRpb24gbW9kZWwgc2V0CiAgICBpbiB0aGlzIGZ1bmN0aW9uICgicHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAiKSwgeW91IG5lZWQgYWNjZXNzIGZvciB0aGVzZSB0d28gbW9kZWxzOgoKICAgICogaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9weWFubm90ZS9zZWdtZW50YXRpb24tMy4wCiAgICAqIGh0dHBzOi8vaHVnZ2luZ2ZhY2UuY28vcHlhbm5vdGUvc3BlYWtlci1kaWFyaXphdGlvbi0zLjAKCiAgICBOb3RlOiBUbyBjb250cm9sIHRoZSByZWNvZ25pemVkIHNwZWFrZXJzIGluIHRoZSBkaWFyaXphdGlvbiBvdXRwdXQgeW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgbWV0aG9kczoKCiAgICAqIEZvciBhIGtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IG1heSBzZXQgc3BlYWtlciBsYWJlbHMgdmlhIHRoZSBgc3BlYWtlcnNfbGFiZWxzYCBwYXJhbWV0ZXIgdGhhdCB3aWxsIGJlIHVzZWQgaW4KICAgICAgdGhlIG9yZGVyIG9mIHNwZWFraW5nIGluIHRoZSBhdWRpbyAoZmlyc3QgcGVyc29uIHNwZWFraW5nIGJlIHRoZSBmaXJzdCBsYWJlbCBpbiB0aGUgbGlzdCkuIEluIGFkZGl0aW9uLCB5b3UgY2FuIGRvCiAgICAgIGRpYXJpemF0aW9uIHBlciBjaGFubmVsIChzZXR0aW5nIHRoZSBwYXJhbWV0ZXIgYHNlcGFyYXRlX2J5X2NoYW5uZWxzYCB0byBUcnVlKS4gRWFjaCBsYWJlbCB3aWxsIGJlIGFzc2lnbmVkIHRvIGEKICAgICAgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlciAoZmlyc3QgbGFiZWwgdG8gY2hhbm5lbCAwLCBzZWNvbmQgbGFiZWwgdG8gY2hhbm5lbCAxIGFuZCBzbyBvbikuIE5vdGljZSwgdGhpcyB3aWxsCiAgICAgIGluY3JlYXNlIHJ1bnRpbWUuCiAgICAqIEZvciB1bmtub3duIHNwZWFrZXJzIGFtb3VudCwgeW91IGNhbiBzZXQgdGhlIGBzcGVha2VyX3ByZWZpeGAgcGFyYW1ldGVyIHRvIGFkZCBhIHByZWZpeCBmb3IgZWFjaCBzcGVha2VyIG51bWJlci4KICAgICAgWW91IGNhbiBhbHNvIGhlbHAgdGhlIGRpYXJpemF0aW9uIGJ5IHNldHRpbmcgdGhlIHNwZWFrZXJzIHJhbmdlIHZpYSB0aGUgYHNwZWFrZXJzX2Ftb3VudF9yYW5nZWAgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgdGhlIGF1ZGlvIGZpbGVzLCBhIHNpbmdsZSBmaWxlIG9yIGEgbGlzdCBvZiBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgICBPbmUgb2YgdGhlIG9mZmljaWFsIGRpYXJpemF0aW9uIG1vZGVsIG5hbWVzIChyZWZlcnJlZCBhcyBkaWFyaXphdGlvbiBwaXBlbGluZXMpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBweWFubm90ZS5hdWRpb2AgSHVnZ2luZ2ZhY2UgcGFnZS4gRGVmYXVsdDogInB5YW5ub3RlL3NwZWFrZXItZGlhcml6YXRpb24tMy4wIi4KICAgIDpwYXJhbSBhY2Nlc3NfdG9rZW46ICAgICAgICAgQW4gYWNjZXNzIHRva2VuIHRvIHBhc3MgZm9yIHVzaW5nIHRoZSBgcHlhbm5vdGUuYXVkaW9gIG1vZGVscy4gSWYgbm90IHByb3ZpZGVkLCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGxvb2tpbmcgZm9yIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuIElmIE1MUnVuIGlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSwgaXQgd2lsbCBsb29rIGZvciBhIHNlY3JldCAiSFVHR0lOR19GQUNFX0hVQl9UT0tFTiIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgIERldmljZSB0byBsb2FkIHRoZSBtb2RlbC4gQ2FuIGJlIG9uZSBvZiB7ImN1ZGEiLCAiY3B1In0uIERlZmF1bHQgd2lsbCBwcmVmZXIgImN1ZGEiIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBzcGVha2Vyc19sYWJlbHM6ICAgICAgTGFiZWxzIHRvIHVzZSBmb3IgdGhlIHJlY29nbml6ZWQgc3BlYWtlcnMuIERlZmF1bHQ6IG51bWVyaWMgbGFiZWxzICgwLCAxLCAuLi4pLgogICAgOnBhcmFtIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBJZiBlYWNoIHNwZWFrZXIgaXMgc3BlYWtpbmcgaW4gYSBzZXBhcmF0ZSBjaGFubmVsLCB5b3UgY2FuIGRpYXJpemUgZWFjaCBjaGFubmVsIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5lIHRoZSByZXN1bHQgaW50byBhIHNpbmdsZSBkaWFyaXphdGlvbi4gRWFjaCBsYWJlbCBzZXQgaW4gdGhlIGBzcGVha2Vyc19sYWJlbHNgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlciB3aWxsIGJlIGFzc2lnbmVkIHRvIGEgc3BlY2lmaWMgY2hhbm5lbCBieSBvcmRlci4KICAgIDpwYXJhbSBzcGVha2VyX3ByZWZpeDogICAgICAgQSBwcmVmaXggdG8gYWRkIGZvciB0aGUgc3BlYWtlcnMgbGFiZWxzLiBUaGlzIHBhcmFtZXRlciBpcyBpZ25vcmVkIGlmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLiBEZWZhdWx0OiAic3BlYWtlciIuCiAgICA6cGFyYW0gbWluaW11bV9zcGVha2VyczogICAgIFNldCB0aGUgbWluaW11bSBleHBlY3RlZCBhbW91bnQgb2Ygc3BlYWtlcnMgdG8gYmUgaW4gdGhlIGF1ZGlvIGZpbGVzLiBUaGlzIHBhcmFtZXRlciBpcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVkIGlmIGBzcGVha2Vyc19sYWJlbHNgIGlzIG5vdCBOb25lLgogICAgOnBhcmFtIG1heGltdW1fc3BlYWtlcnM6ICAgICBTZXQgdGhlIG1heGltdW0gZXhwZWN0ZWQgYW1vdW50IG9mIHNwZWFrZXJzIHRvIGJlIGluIHRoZSBhdWRpbyBmaWxlcy4gVGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZCBpZiBgc3BlYWtlcnNfbGFiZWxzYCBpcyBub3QgTm9uZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgV2hldGhlciB0byBwcmVzZW50IGxvZ3Mgb2YgYSBwcm9ncmVzcyBiYXIgYW5kIGVycm9ycy4gRGVmYXVsdDogVHJ1ZS4KCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBTcGVlY2ggZGlhcml6YXRpb24gZGljdGlvbmFyeS4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgdHJhbnNjcmliZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IGF1ZGlvIGZpbGVzIHRvIGRpYXJpemU6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICAgICAgYXVkaW9fZmlsZXMgPSBfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCiAgICBlbHNlOiAgIyBTaG91bGQgYmUgYSBsaXN0IG9mIGZpbGVzLgogICAgICAgIGF1ZGlvX2ZpbGVzID0gZGF0YV9wYXRoCgogICAgIyBHZXQgdGhlIEh1Z2dpbmdmYWNlIGFjY2VzcyB0b2tlbjoKICAgIGFjY2Vzc190b2tlbiA9IF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcj1hY2Nlc3NfdG9rZW4pCiAgICBpZiBhY2Nlc3NfdG9rZW4gaXMgTm9uZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiQSBIdWdnaW5nZmFjZSBhY2Nlc3MgdG9rZW4gbXVzdCBiZSBwcm92aWRlZCB0byB1c2UgYHB5YW5ub3RlLmF1ZGlvYCBtb2RlbHMuIEFjY2VzcyB0b2tlbiBjYW4gYmUgcGFzc2VkICIKICAgICAgICAgICAgInZpYSBvbmUgb2YgdGhlIGZvbGxvd2luZyBvcHRpb25zOlxuIgogICAgICAgICAgICAiKiBVc2UgdGhlIHBhcmFtZXRlciBgYWNjZXNzX3Rva2VuYC5cbiIKICAgICAgICAgICAgIiogU2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlIG5hbWVkICdIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOJy5cbiIKICAgICAgICAgICAgIiogSWYgdXNpbmcgTUxSdW4sIHlvdSBjYW4gcGFzcyBpdCBhcyBhIHNlY3JldCBuYW1lZCAnSFVHR0lOR19GQUNFX0hVQl9UT0tFTicuIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGRpYXJpemF0aW9uIHBpcGVsaW5lOgogICAgcGlwZWxpbmUgPSBweWFubm90ZS5hdWRpby5QaXBlbGluZS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgY2hlY2twb2ludF9wYXRoPW1vZGVsX25hbWUsIHVzZV9hdXRoX3Rva2VuPWFjY2Vzc190b2tlbgogICAgKQoKICAgICMgU2V0IHRoZSBkZXZpY2U6CiAgICBkZXZpY2UgPSBkZXZpY2Ugb3IgKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBpZiBkZXZpY2UgIT0gImNwdSI6CiAgICAgICAgcGlwZWxpbmUudG8odG9yY2guZGV2aWNlKGRldmljZSkpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIGRpYXJpemF0aW9ucyA9IHt9CiAgICBlcnJvcnMgPSB7fQoKICAgICMgUHJlcGFyZSB0aGUgZGlhcml6YXRpb24ga2V5d29yZCBhcmd1bWVudHM6CiAgICBkaWFyaXplX2t3YXJncyA9IHt9CiAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm51bV9zcGVha2VycyJdID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgIGVsc2U6CiAgICAgICAgaWYgbWluaW11bV9zcGVha2VyczoKICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3NbIm1pbl9zcGVha2VycyJdID0gbWluaW11bV9zcGVha2VycwogICAgICAgIGlmIG1heGltdW1fc3BlYWtlcnM6CiAgICAgICAgICAgIGRpYXJpemVfa3dhcmdzWyJtYXhfc3BlYWtlcnMiXSA9IG1heGltdW1fc3BlYWtlcnMKCiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCBkaWFyaXplOgogICAgZm9yIGF1ZGlvX2ZpbGUgaW4gdHFkbSgKICAgICAgICBhdWRpb19maWxlcywgZGVzYz0iRGlhcml6aW5nIiwgdW5pdD0iZmlsZSIsIGRpc2FibGU9bm90IHZlcmJvc2UKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICAjIExvYWQgYXVkaW8gZmlsZToKICAgICAgICAgICAgYXVkaW8sIHNhbXBsZV9yYXRlID0gdG9yY2hhdWRpby5sb2FkKHVyaT1hdWRpb19maWxlLCBjaGFubmVsc19maXJzdD1UcnVlKQogICAgICAgICAgICAjIEdldCB0aGUgZGlhcml6YXRpb24gKGlmIHByb3ZpZGVkKToKICAgICAgICAgICAgZGlhcml6YXRpb25zW2F1ZGlvX2ZpbGUubmFtZV0gPSBfZGlhcml6ZSgKICAgICAgICAgICAgICAgIGF1ZGlvPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVscz1zcGVha2Vyc19sYWJlbHMsCiAgICAgICAgICAgICAgICBzZXBhcmF0ZV9ieV9jaGFubmVscz1zZXBhcmF0ZV9ieV9jaGFubmVscywKICAgICAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICAgICAgZGlhcml6ZV9rd2FyZ3M9ZGlhcml6ZV9rd2FyZ3MsCiAgICAgICAgICAgICkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgIyBOb3RlIHRoZSBleGNlcHRpb24gYXMgZXJyb3IgaW4gdGhlIGRpY3Rpb25hcnk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBfTE9HR0VSLndhcm5pbmcoZiJFcnJvciBpbiBmaWxlOiAne2F1ZGlvX2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgZXJyb3JzW3N0cihhdWRpb19maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oZGlhcml6YXRpb25zKX0ve2xlbihhdWRpb19maWxlcyl9KVxuIikKICAgIHJldHVybiBkaWFyaXphdGlvbnMsIGVycm9ycwoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9nZXRfYWNjZXNzX3Rva2VuKHBhcmFtZXRlcjogc3RyKSAtPiBzdHI6CiAgICAjIElmIGdpdmVuIGFzIGEgcGFyYW1ldGVyLCByZXR1cm4gaXQ6CiAgICBpZiBwYXJhbWV0ZXI6CiAgICAgICAgcmV0dXJuIHBhcmFtZXRlcgoKICAgICMgT3RoZXJ3aXNlLCBsb29rIGF0IHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZToKICAgIGVudmlyb25tZW50X3ZhcmlhYmxlID0gb3MuZW52aXJvbi5nZXQoIkhVR0dJTkdfRkFDRV9IVUJfVE9LRU4iKQogICAgaWYgZW52aXJvbm1lbnRfdmFyaWFibGU6CiAgICAgICAgcmV0dXJuIGVudmlyb25tZW50X3ZhcmlhYmxlCgogICAgIyBMYXN0bHksIHRyeSBsb29rIGluIHRoZSBzZXQgc2VjcmV0cyBpbiBNTFJ1bjoKICAgIHNlY3JldCA9IE5vbmUKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBzZWNyZXQgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5PSJIVUdHSU5HX0ZBQ0VfSFVCX1RPS0VOIikKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHBhc3MKCiAgICByZXR1cm4gc2VjcmV0CgoKZGVmIF9kaWFyaXplKAogICAgYXVkaW86IHRvcmNoLlRlbnNvciwKICAgIHNhbXBsZV9yYXRlOiBpbnQsCiAgICBwaXBlbGluZTogcHlhbm5vdGUuYXVkaW8uUGlwZWxpbmUsCiAgICBzcGVha2Vyc19sYWJlbHM6IExpc3Rbc3RyXSwKICAgIHNlcGFyYXRlX2J5X2NoYW5uZWxzOiBib29sLAogICAgc3BlYWtlcl9wcmVmaXg6IHN0ciwKICAgIGRpYXJpemVfa3dhcmdzOiBkaWN0LAopIC0+IExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXToKICAgICMgSWYgdGhlcmUgaXMgbm8gbmVlZCBmb3Igc2VwYXJhdGlvbiBieSBjaGFubmVscywgd2UgZGlhcml6ZSBhbmQgcmV0dXJuOgogICAgaWYgbm90IHNlcGFyYXRlX2J5X2NoYW5uZWxzOgogICAgICAgICMgRGlhcml6ZToKICAgICAgICBkaWFyaXphdGlvbjogcHlhbm5vdGUuY29yZS5Bbm5vdGF0aW9uID0gcGlwZWxpbmUoCiAgICAgICAgICAgIGZpbGU9eyJ3YXZlZm9ybSI6IGF1ZGlvLCAic2FtcGxlX3JhdGUiOiBzYW1wbGVfcmF0ZX0sICoqZGlhcml6ZV9rd2FyZ3MKICAgICAgICApCiAgICAgICAgIyBWZXJpZnkgc3BlYWtlcnMgbGFiZWxzIChzaG91bGQgbm90IGZhaWwgaGVyZSBhcyB3ZSBzZXQgYG51bV9zcGVha2Vycz1sZW4oc3BlYWtlcnNfbGFiZWxzKWAgd2hlbiBpbmZlcnJpbmcKICAgICAgICAjIHRocm91Z2ggdGhlIHBpcGVsaW5lKToKICAgICAgICBpZiBzcGVha2Vyc19sYWJlbHM6CiAgICAgICAgICAgIGdpdmVuX3NwZWFrZXJzID0gbGVuKHNwZWFrZXJzX2xhYmVscykKICAgICAgICAgICAgZm91bmRfc3BlYWtlcnMgPSBsZW4oc2V0KGRpYXJpemF0aW9uLmxhYmVscygpKSkKICAgICAgICAgICAgaWYgZ2l2ZW5fc3BlYWtlcnMgPCBmb3VuZF9zcGVha2VyczoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJOb3QgZW5vdWdoIGBzcGVha2Vyc19sYWJlbHNgIHdlcmUgZ2l2ZW4uIEdvdCB7Z2l2ZW5fc3BlYWtlcnN9IGxhYmVscyBidXQgdGhlIGRpYXJpemF0aW9uICIKICAgICAgICAgICAgICAgICAgICBmInJlY29nbml6ZWQge2ZvdW5kX3NwZWFrZXJzfSBzcGVha2Vycy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBSZXR1cm4gYXMgYSBkaWFyaXphdGlvbiBsaXN0IC0gYSBzb3J0ZWQgbGlzdCBvZiB0dXBsZXMgb2Ygc3RhcnQgdGltZSwgZW5kIHRpbWUgYW5kIGEgbGFiZWwgKHRoZSBkZWZhdWx0IGxhYmVsCiAgICAgICAgIyByZXR1cm5lZCBpcyAiU1BFQUtFUl9pIiBzbyB3ZSB0YWtlIG9ubHkgdGhlIGluZGV4IG91dCBvZiBpdCk6CiAgICAgICAgcmV0dXJuIFsKICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgc2VnbWVudC5zdGFydCwKICAgICAgICAgICAgICAgIHNlZ21lbnQuZW5kLAogICAgICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzW2ludChsYWJlbC5zcGxpdCgiXyIpWzFdKV0KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJzX2xhYmVscwogICAgICAgICAgICAgICAgZWxzZSBmIntzcGVha2VyX3ByZWZpeH17aW50KGxhYmVsLnNwbGl0KCdfJylbMV0pfSIsCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIHNlZ21lbnQsIHRyYWNrLCBsYWJlbCBpbiBkaWFyaXphdGlvbi5pdGVydHJhY2tzKHlpZWxkX2xhYmVsPVRydWUpCiAgICAgICAgXQoKICAgICMgU2VwYXJhdGUgdG8gY2hhbm5lbHMgYW5kIGRpYXJpemUgKHdlIGV4cGVjdCBvbmx5IG9uZSBzcGVha2VyIHBlciBjaGFubmVsKToKICAgIGNoYW5uZWxfZGlhcml6YXRpb25zID0gWwogICAgICAgIF9kaWFyaXplKAogICAgICAgICAgICBhdWRpbz1hdWRpb1tjaGFubmVsXS51bnNxdWVlemUoCiAgICAgICAgICAgICAgICAwCiAgICAgICAgICAgICksICAjIFRha2UgY2hhbm5lbCBhbmQgYWRkIGEgY2hhbm5lbCBkaW1lbnNpb24gdG8gaXQuCiAgICAgICAgICAgIHNhbXBsZV9yYXRlPXNhbXBsZV9yYXRlLAogICAgICAgICAgICBwaXBlbGluZT1waXBlbGluZSwKICAgICAgICAgICAgc3BlYWtlcnNfbGFiZWxzPVsKICAgICAgICAgICAgICAgIHNwZWFrZXJzX2xhYmVsc1tjaGFubmVsXQogICAgICAgICAgICBdLCAgIyBUYWtlIHRoZSBjaGFubmVsJ3MgbGFiZWwgb25seS4KICAgICAgICAgICAgc2VwYXJhdGVfYnlfY2hhbm5lbHM9RmFsc2UsCiAgICAgICAgICAgIHNwZWFrZXJfcHJlZml4PXNwZWFrZXJfcHJlZml4LAogICAgICAgICAgICBkaWFyaXplX2t3YXJncz17Im51bV9zcGVha2VycyI6IDF9LCAgIyBTZXQgdG8gb25lIHNwZWFrZXIuCiAgICAgICAgKQogICAgICAgIGZvciBjaGFubmVsIGluIHJhbmdlKGF1ZGlvLnNoYXBlWzBdKQogICAgXQoKICAgICMgTWVyZ2UgdGhlIGNoYW5uZWwgZGlhcml6YXRpb25zIGludG8gYSBzaW5nbGUgc29ydGVkIGxpc3Q6CiAgICByZXR1cm4gbGlzdChoZWFwcS5tZXJnZSgqY2hhbm5lbF9kaWFyaXphdGlvbnMpKQo=
    +  default_handler: diarize
       entry_points:
         open_mpi_handler:
           name: open_mpi_handler
    -      doc: ''
    +      has_varargs: false
    +      lineno: 61
           parameters:
           - name: worker_inputs
             type: List[str]
           - name: root_worker_inputs
             type: Dict[str, Any]
             default: null
    -      outputs: []
    -      lineno: 61
    -      has_varargs: false
           has_kwargs: false
    +      doc: ''
         decorator:
           name: decorator
    -      doc: ''
    +      has_varargs: false
    +      lineno: 73
           parameters:
           - name: handler
    -      outputs: []
    -      lineno: 73
    -      has_varargs: false
           has_kwargs: false
    +      doc: ''
         wrapper:
           name: wrapper
    -      doc: ''
    -      parameters: []
    -      outputs: []
    -      lineno: 78
           has_varargs: false
    +      lineno: 78
           has_kwargs: true
    +      doc: ''
         diarize:
           name: diarize
    -      doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\
    -        The end result is a dictionary with the file names as keys and their diarization\
    -        \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\
    -        \nTo use the `pyannote.audio` models you must pass a Huggingface token and\
    -        \ get access to the required models. The\ntoken can be passed in one of the\
    -        \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\
    -        \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\
    -        \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\
    -        \ models on Huggingface, visit their page. For example, to use the default\
    -        \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\
    -        ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\
    -        * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\
    -        \ the recognized speakers in the diarization output you can choose one of\
    -        \ the following methods:\n\n* For a known speakers amount, you may set speaker\
    -        \ labels via the `speakers_labels` parameter that will be used in\n  the order\
    -        \ of speaking in the audio (first person speaking be the first label in the\
    -        \ list). In addition, you can do\n  diarization per channel (setting the parameter\
    -        \ `separate_by_channels` to True). Each label will be assigned to a\n  specific\
    -        \ channel by order (first label to channel 0, second label to channel 1 and\
    -        \ so on). Notice, this will\n  increase runtime.\n* For unknown speakers amount,\
    -        \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\
    -        \ number.\n  You can also help the diarization by setting the speakers range\
    -        \ via the `speakers_amount_range` parameter."
    +      has_varargs: false
    +      lineno: 139
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]
           parameters:
           - name: data_path
             type: Union[str, List[str]]
    @@ -162,22 +129,37 @@
             type: bool
             doc: 'Whether to present logs of a progress bar and errors. Default: True.'
             default: false
    -      outputs:
    -      - doc: 'A tuple of:'
    -        type: Tuple[Dict[str, List[Tuple[float, float, str]]], Dict[str, str]]
    -      lineno: 139
    -      has_varargs: false
           has_kwargs: false
    +      doc: "Perform speech diarization on given audio files using pyannote-audio (https://github.com/pyannote/pyannote-audio).\n\
    +        The end result is a dictionary with the file names as keys and their diarization\
    +        \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\
    +        \nTo use the `pyannote.audio` models you must pass a Huggingface token and\
    +        \ get access to the required models. The\ntoken can be passed in one of the\
    +        \ following options:\n\n* Use the parameter `access_token`.\n* Set an environment\
    +        \ variable named \"HUGGING_FACE_HUB_TOKEN\".\n* If using MLRun, you can pass\
    +        \ it as a secret named \"HUGGING_FACE_HUB_TOKEN\".\n\nTo get access to the\
    +        \ models on Huggingface, visit their page. For example, to use the default\
    +        \ diarization model set\nin this function (\"pyannote/speaker-diarization-3.0\"\
    +        ), you need access for these two models:\n\n* https://huggingface.co/pyannote/segmentation-3.0\n\
    +        * https://huggingface.co/pyannote/speaker-diarization-3.0\n\nNote: To control\
    +        \ the recognized speakers in the diarization output you can choose one of\
    +        \ the following methods:\n\n* For a known speakers amount, you may set speaker\
    +        \ labels via the `speakers_labels` parameter that will be used in\n  the order\
    +        \ of speaking in the audio (first person speaking be the first label in the\
    +        \ list). In addition, you can do\n  diarization per channel (setting the parameter\
    +        \ `separate_by_channels` to True). Each label will be assigned to a\n  specific\
    +        \ channel by order (first label to channel 0, second label to channel 1 and\
    +        \ so on). Notice, this will\n  increase runtime.\n* For unknown speakers amount,\
    +        \ you can set the `speaker_prefix` parameter to add a prefix for each speaker\
    +        \ number.\n  You can also help the diarization by setting the speakers range\
    +        \ via the `speakers_amount_range` parameter."
       description: pyannote's speech diarization of audio files
    -  default_handler: diarize
    -  disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    +metadata:
    +  name: pyannote-audio
    +  tag: ''
    +  categories:
    +  - deep-learning
    +  - audio
     verbose: false
     
             
    diff --git a/functions/master/pyannote_audio/latest/static/item.html b/functions/master/pyannote_audio/latest/static/item.html
    index b3e1fd35..2fbd98e6 100644
    --- a/functions/master/pyannote_audio/latest/static/item.html
    +++ b/functions/master/pyannote_audio/latest/static/item.html
    @@ -31,7 +31,6 @@
     apiVersion: v1
     categories:
     - deep-learning
    -- huggingface
     - audio
     description: pyannote's speech diarization of audio files
     doc: ''
    @@ -43,7 +42,7 @@
       author: guyl
     maintainers: []
     marketplaceType: ''
    -mlrunVersion: 1.5.2
    +mlrunVersion: 1.7.0
     name: pyannote-audio
     platformVersion: 3.5.3
     spec:
    @@ -57,7 +56,7 @@
       - torchaudio
       - tqdm
     url: ''
    -version: 1.2.0
    +version: 1.3.0
     
             
         
    diff --git a/functions/master/pyannote_audio/latest/static/pyannote_audio.html b/functions/master/pyannote_audio/latest/static/pyannote_audio.html index b73f1595..2d9f2aa3 100644 --- a/functions/master/pyannote_audio/latest/static/pyannote_audio.html +++ b/functions/master/pyannote_audio/latest/static/pyannote_audio.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/question_answering/0.5.0/src/data/test-data.txt b/functions/master/question_answering/0.5.0/src/data/test-data.txt new file mode 100644 index 00000000..efe6b646 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/data/test-data.txt @@ -0,0 +1 @@ +The apple color is red. \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/src/function.yaml b/functions/master/question_answering/0.5.0/src/function.yaml new file mode 100644 index 00000000..21f741aa --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/function.yaml @@ -0,0 +1,197 @@ +metadata: + name: question-answering + tag: '' + categories: + - genai +verbose: false +kind: job +spec: + command: '' + default_handler: answer_questions + build: + origin_filename: '' + base_image: mlrun/mlrun + requirements: + - transformers + - torch + - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K + entry_points: + open_mpi_handler: + name: open_mpi_handler + has_varargs: false + doc: '' + lineno: 58 + parameters: + - name: worker_inputs + type: List[str] + - name: root_worker_inputs + type: Dict[str, Any] + default: null + has_kwargs: false + decorator: + name: decorator + has_varargs: false + doc: '' + lineno: 66 + parameters: + - name: handler + has_kwargs: false + wrapper: + name: wrapper + has_varargs: false + doc: '' + lineno: 71 + has_kwargs: true + answer_questions: + outputs: + - doc: 'A tuple of:' + type: Tuple[pd.DataFrame, dict] + name: answer_questions + has_varargs: false + doc: 'Answer questions with a context to the given text files contents by a + pretrained LLM model. Each text file will have + + the following prompt built: + + + start of `text_wrapper` + + + + end of `text_wrapper` + + + start of `questions_wrapper` + + 1. + + 2. + + ... + + n. + + end of `questions_wrapper`' + lineno: 130 + parameters: + - name: data_path + type: Union[str, List[str]] + doc: A path to a directory of text files or a path to a text file to ask questions + about. + - name: model_name + type: str + doc: The pre-trained model name from the huggingface hub to use for asking + questions. + - name: questions + type: Union[List[str], List[List[str]]] + doc: The questions to ask. A list of lists of questions to ask per text file, + and devided by question groups, the groups can be dtermained by size (in + order to avoid large inputs to the llm) or by questioning method (regular + or poll like questioning). + - name: device_map + type: Union[str, dict] + doc: A map to use for loading the model on multiple devices. + default: null + - name: model_kwargs + type: dict + doc: Keyword arguments to pass for loading the model using HuggingFace's `transformers.AutoModelForCausalLM.from_pretrained` + function. + default: null + - name: auto_gptq_exllama_max_input_length + type: int + doc: For AutoGPTQ models to set and extend the model's input buffer size. + default: null + - name: tokenizer_name + type: str + doc: The tokenizer name from the huggingface hub to use. If not given, the + model name will be used. + default: null + - name: tokenizer_kwargs + type: dict + doc: Keyword arguments to pass for loading the tokenizer using HuggingFace's + `transformers.AutoTokenizer.from_pretrained` function. + default: null + - name: text_wrapper + type: Union[str, List[str]] + doc: A wrapper for the file's text. Will be added at the start of the prompt. + Must have a placeholder ('{}') for the text of the file. + default: '' + - name: questions_wrapper + type: Union[str, List[str]] + doc: A wrapper for the questions received. Will be added after the text wrapper + in the prompt template. Must have a placeholder ('{}') for the questions. + default: '' + - name: generation_config + type: Union[Dict, List[Dict]] + doc: HuggingFace's `GenerationConfig` keyword arguments to pass to the `generate` + method. + default: null + - name: questions_config + type: Union[Dict, List[Dict]] + doc: A dictionary or list of dictionaries containing specific ways to answer + questions (using a poll for example), each dictionary in the list is for + corresponding question group and determines the question asking method for + said group. + default: null + - name: batch_size + type: int + doc: Batch size for inference. + default: 1 + - name: questions_columns + type: List[str] + doc: Columns to use for the dataframe returned. + default: null + - name: verbose + type: bool + doc: 'Whether to present logs of a progress bar and errors. Default: True.' + default: false + has_kwargs: false + answer: + outputs: + - type: List[List[str]] + name: answer + has_varargs: false + doc: Answer questions with a context to the given text files contents by a pretrained + LLM model in given pipeline. + lineno: 674 + parameters: + - name: self + - name: questions_amount + type: int + - name: batched_input + type: List[str] + - name: generation_pipeline + type: Pipeline + - name: generation_config + type: GenerationConfig + has_kwargs: false + most_common: + name: most_common + has_varargs: false + doc: Calculate the most common answer for a given list of answers. + lineno: 637 + parameters: + - name: answers + has_kwargs: false + average: + name: average + has_varargs: false + doc: Calculate the average answer for a given list of answers. + lineno: 646 + parameters: + - name: answers + has_kwargs: false + do: + name: do + has_varargs: false + doc: Perform the strategy. + lineno: 662 + parameters: + - name: self + - name: answers + has_kwargs: false + image: '' + description: GenAI approach of question answering on a given data + disable_auto_mount: false diff --git a/functions/master/question_answering/0.5.0/src/item.yaml b/functions/master/question_answering/0.5.0/src/item.yaml new file mode 100755 index 00000000..741bab80 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/item.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +categories: +- genai +description: GenAI approach of question answering on a given data +doc: '' +example: question_answering.ipynb +generationDate: 2023-08-07:11-30 +hidden: false +icon: '' +labels: + author: yonish +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: question_answering +platformVersion: 3.5.0 +spec: + filename: question_answering.py + handler: answer_questions + image: mlrun/mlrun + kind: job + requirements: + - transformers + - torch + - tqdm +url: '' +version: 0.5.0 diff --git a/functions/master/question_answering/0.5.0/src/question_answering.ipynb b/functions/master/question_answering/0.5.0/src/question_answering.ipynb new file mode 100644 index 00000000..7c506688 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/question_answering.ipynb @@ -0,0 +1,903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "75860292-80d3-4dfb-89e4-66579321c78b", + "metadata": {}, + "source": [ + "# Question Answering" + ] + }, + { + "cell_type": "markdown", + "id": "4593a39d-6e91-4f92-9e7e-09dcd7dbcab7", + "metadata": {}, + "source": [ + "## Short description and explenation" + ] + }, + { + "cell_type": "markdown", + "id": "14dc0595-8b8a-4a13-b6a7-2a1bc43d8d50", + "metadata": {}, + "source": [ + "This function enables ad-hoc question answering over documents by ingesting text into a language model and returning formatted responses.
    \n", + "It accepts:
    \n", + "\n", + "* A language model
    \n", + "* Text files with content
    \n", + "* Questions to answer
    \n", + "* More inputs can be given for configuration
    \n", + "\n", + "The model processes the files to build understanding. Questions posed are then answered in one of two modes:\n", + "\n", + "Default mode:
    \n", + "The model directly answers each question using its own capabilities.\n", + "\n", + "Poll mode:
    \n", + "Additional models are included to separately answer each question. An aggregation algorithm determines the best response through consensus between models.
    \n", + "Two options exist for consensus methodology:
    \n", + "\n", + "Average Answer:
    \n", + "Each model's answer is scored. The response with the average highest score amongst models is selected. Useful for numeric or ranked responses.\n", + "\n", + "Most Common Answer:
    The answer that occurs most frequently across models is selected. Useful for textual responses to avoid outliers.\n", + "\n", + "Using multiple models via the poll mode provides accuracy improvements for questions lacking definitive answers, as it refines responses through an ensemble process.
    " + ] + }, + { + "cell_type": "markdown", + "id": "ae957ac3-2c26-4a0b-8e44-8315caeb2953", + "metadata": {}, + "source": [ + "## Background" + ] + }, + { + "cell_type": "markdown", + "id": "3a351565-6f2c-4fa3-a024-4b5d658db311", + "metadata": {}, + "source": [ + "At the core, advanced natural language processing (NLP) models called foundation models are being leveraged to read and comprehend the input text files.
    \n", + "Specifically, models such as GPT-3 or Codex from Anthropic are used as the base language model.\n", + "\n", + "When documents are fed into the function, the background process invokes these models to ingest and digest the information.
    \n", + "\n", + "This provides the knowledge base for the models to then offer informed answers tailored to any queries about the documents.
    \n", + "The parameters controlling model size and computation time provide tradeoffs between cost, speed, and sophistication of comprehension.\n", + "\n", + "Additionally, the poll option expands on a single model by sampling responses from a number of models as mentioned above.
    " + ] + }, + { + "cell_type": "markdown", + "id": "a6fc4aaa-530a-4e9e-8447-737a0cfd6ed5", + "metadata": {}, + "source": [ + "## Requirements" + ] + }, + { + "cell_type": "markdown", + "id": "685d9000-37e1-462b-93c8-1bbfcdf6aaa1", + "metadata": {}, + "source": [ + "`transformers`
    \n", + "`torch`
    \n", + "`tqdm`
    " + ] + }, + { + "cell_type": "markdown", + "id": "73d9b369-1c36-42e8-b106-491ad911f281", + "metadata": {}, + "source": [ + "## Documentation" + ] + }, + { + "cell_type": "markdown", + "id": "68e3a54d-0cd9-4845-ae14-f24068052bf3", + "metadata": {}, + "source": [ + "`data_path`: A path to a directory of text files or a path to a text file to ask questions about.
    \n", + "\n", + "`model_name`: The pre-trained model name from the huggingface hub to use for answering questions.
    \n", + "\n", + "`questions`: The questions to ask. A list of lists of questions to ask per text file, and devided
    \n", + " by question groups, the groups can be determained by size (in order to
    \n", + " avoid large inputs to the llm) or by questioning method (regular or poll like questioning).
    \n", + " \n", + "`device_map`: A map to use for loading the model on multiple devices.
    \n", + "\n", + "`model_kwargs`: Keyword arguments to pass for loading the model using HuggingFace's
    \n", + " _transformers.AutoModelForCausalLM.from_pretrained_ function.
    \n", + " \n", + "`auto_gptq_exllama_max_input_length`: For AutoGPTQ models to set and extend the model's input buffer size.
    \n", + "\n", + "`tokenizer_name`: The tokenizer name from the huggingface hub to use. If not given, the given model name will be used.
    \n", + " \n", + "`tokenizer_kwargs`: Keyword arguments to pass for loading the tokenizer using HuggingFace's
    \n", + " _transformers.AutoTokenizer.from_pretrained_ function.
    \n", + " \n", + "`text_wrapper`: Must have a placeholder ('{}') for the text of the file.
    \n", + "\n", + "`questions_wrapper`: A wrapper for the questions received. Will be added after the text wrapper in the prompt template.
    \n", + " Must have a placeholder ('{}') for the questions.
    \n", + " \n", + "`generation_config`: HuggingFace's _GenerationConfig_ keyword arguments to pass to the _generate_ method.
    \n", + " \n", + "`questions_config`: A dictionary or list of dictionaries containing specific ways to answer questions (using a poll for example),
    \n", + " each dictionary in the list is for corresponding question group and determines the question asking method
    \n", + " for said group.
    \n", + " \n", + "`batch_size`: Batch size for inference.
    \n", + "\n", + "`questions_columns`: Columns to use for the dataframe returned.
    \n", + "\n", + "`verbose`: Whether to present logs of a progress bar and errors. Default: True.
    \n" + ] + }, + { + "cell_type": "markdown", + "id": "716e5fac-3def-4cdd-8ca5-d1c93ee64f2e", + "metadata": {}, + "source": [ + "## Demo 1" + ] + }, + { + "cell_type": "markdown", + "id": "3bf4bc9b-fc5e-4155-8563-0575c22cef05", + "metadata": {}, + "source": [ + "This is a short and simple example to show the basic use of the function." + ] + }, + { + "cell_type": "markdown", + "id": "c95dcfdb-22e1-4b82-b0a3-9c89487a216f", + "metadata": {}, + "source": [ + "### (1.) Import the function (import mlrun, set project and import function)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60161e5f-468c-47c9-be98-e6554b899c9c", + "metadata": {}, + "outputs": [], + "source": [ + "import mlrun\n", + "import transformers\n", + "import tempfile" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1267b60b-35d1-48bf-8ea0-dfe7a5f366e7", + "metadata": {}, + "outputs": [], + "source": [ + "project = mlrun.get_or_create_project(\n", + " name=\"call-center-demo-1\",\n", + " context=\"./\",\n", + " user_project=True,\n", + " parameters={\n", + " \"default_image\": \"mlrun/mlrun\",\n", + " })" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05c8b39b-433c-40b8-9260-94923c9cbb6c", + "metadata": {}, + "outputs": [], + "source": [ + "func = project.set_function(\n", + " \"question-answering.py\",\n", + " name=\"question-answering\",\n", + " kind=\"job\",\n", + " handler=\"answer_questions\",\n", + ")\n", + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "b9744a13-6530-4aa0-a30c-a88db94ce853", + "metadata": {}, + "source": [ + "We create a text file that the model can be asked about" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "503b874a-0c64-4a66-9b30-fe99191b5fd3", + "metadata": {}, + "outputs": [], + "source": [ + "def _make_data_dir_for_test():\n", + " data_dir = tempfile.mkdtemp()\n", + " # The information the model will need in order to answer our question\n", + " content = \"The apple is red.\"\n", + " with open(data_dir + \"/test_data.txt\", \"w\") as f:\n", + " f.write(content)\n", + " return data_dir" + ] + }, + { + "cell_type": "markdown", + "id": "7fadd06e-210b-45aa-b7ea-686058b6e7f4", + "metadata": {}, + "source": [ + "### (2.) Usage\n", + "Then we set where to take the path to the text file we want to ask about, the questions, and column name for the answer table." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a634b19-d809-4436-bbdd-469fc1d61c6e", + "metadata": {}, + "outputs": [], + "source": [ + "input_path = _make_data_dir_for_test()\n", + "# The question for the model to answer\n", + "question = [\"What is the color of the apple?\"]\n", + "# The column of the answer in the data frame returned by the function\n", + "column_name = [\"color\"]" + ] + }, + { + "cell_type": "markdown", + "id": "0364ce68-079e-4769-89b6-661fcdc1d475", + "metadata": {}, + "source": [ + "Now we run the function with all the parameters we prepered earlier" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "448bada9-8b52-4175-9839-ecb409ab3e35", + "metadata": {}, + "outputs": [], + "source": [ + "demo1_run = func.run(\n", + " handler=\"answer_questions\",\n", + " params={\n", + " \"model\": \"distilgpt2\",\n", + " \"input_path\": input_path,\n", + " \"questions\": question,\n", + " \"questions_columns\": column_name,\n", + " \"generation_config\": {\n", + " \"do_sample\": True,\n", + " \"temperature\": 0.8,\n", + " \"top_p\": 0.9,\n", + " \"early_stopping\": True,\n", + " \"max_new_tokens\": 20,\n", + " },\n", + " },\n", + " returns=[\n", + " \"question_answering_df: dataset\",\n", + " \"question_answering_errors: result\",\n", + " ],\n", + " local=True,\n", + " artifact_path=\"./\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "474505db-2fc8-48fd-a634-2bada802a449", + "metadata": {}, + "source": [ + "### (3.) Review results\n", + "and after the run is finished we can take a look and see our answer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4560b51d-5f96-465d-9826-e88c7d4d46aa", + "metadata": {}, + "outputs": [], + "source": [ + "demo1_run.outputs" + ] + }, + { + "cell_type": "markdown", + "id": "31a401a5-2f8a-427f-bf62-2f31f94f5ee7", + "metadata": {}, + "source": [ + "## Demo 2" + ] + }, + { + "cell_type": "markdown", + "id": "503b8a40-ad61-445f-900b-4fdaa036e417", + "metadata": {}, + "source": [ + "This is a much larger example, we will show how we use this function to analyze a number of calls between agents and customer of a internet company (all the data is generated by Iguazio).
    \n", + "For something like this, we recomend using a strong model, and putting some time into making the prompts." + ] + }, + { + "cell_type": "markdown", + "id": "759c521b-df3d-498f-8642-863182107618", + "metadata": {}, + "source": [ + "### (1.) Import the function (import mlrun, set project and import function)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bde6a480-a3d9-4b8c-a9c0-daa235f0f0c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-18 10:18:37,490 [warning] Client version with higher version than server version isn't supported, align your client to the server version: {'parsed_server_version': Version(major=1, minor=5, patch=2, prerelease='rc1', build='track'), 'parsed_client_version': Version(major=1, minor=6, patch=0, prerelease='rc11', build=None)}\n" + ] + } + ], + "source": [ + "import os\n", + "import mlrun\n", + "import torch\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "187a4643-53e9-40bb-a337-5096df7946d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-18 10:18:51,651 [info] Project loaded successfully: {'project_name': 'call-center-demo-zeev55'}\n" + ] + } + ], + "source": [ + "project = mlrun.get_or_create_project(\n", + " name=\"call-center-demo-2\",\n", + " context=\"./\",\n", + " user_project=True,\n", + " parameters={\n", + " \"default_image\": \"mlrun/mlrun\",\n", + " })\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "17eb7783-9ced-482b-9bdf-c41e55995faf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "func = project.set_function(\n", + " \"question-answering.py\",\n", + " name=\"question-answering\",\n", + " kind=\"job\",\n", + " handler=\"answer_questions\",\n", + ")\n", + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "91d3ebb2-7d4a-4e52-89ed-45287c06eb76", + "metadata": {}, + "source": [ + "### (2.) Usage\n", + "\n", + "This example is a bit more complicated as we mentioned, we give the model a list of questions, for some of them we give the model a list of answers to choose from." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2bc065e4-2dbf-4d7a-9772-6b7039f428bc", + "metadata": {}, + "outputs": [], + "source": [ + "QUESTIONS = [\n", + " \"1. Write a long summary of the text, focus on the topic (max 50 words).\",\n", + " \"2. Was the Client's concern addressed, (choose only one) [Yes, No]?\",\n", + " ]\n", + "\n", + "qa_questions_columns = [\n", + " \"Summary\",\n", + " \"is_fixed\",\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "id": "aa89f316-0d1b-4ada-9990-d2293546eee3", + "metadata": {}, + "source": [ + "Another thing we give the model this time is answer examples (one/few shot answering), this can be done to show the model how you want the answer to be structured or caculated.
    \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbc093ad-dab4-46a1-b36a-2a7551cef018", + "metadata": {}, + "outputs": [], + "source": [ + "# For every file we ask about, the model will be presented with this example of a call and how we want the answers.\n", + "DEMO_CALL = (\n", + " \"Agent: Good afternoon, you've reached [Internet Service Provider] customer support. I'm Megan. How can I assist \"\n", + " \"you today?\\n\"\n", + " \"Customer: Hello, Megan. This is Lisa. I've noticed some billing discrepancies on my last statement.\\n\"\n", + " \"Agent: Thank you, Lisa. Let me pull up your account. I see the billing discrepancies you mentioned. It appears \"\n", + " \"there was an error in the charges. I apologize for the inconvenience.\\n\"\n", + " \"Customer: Thank you for acknowledging the issue, Megan. Can you please help me get it resolved?\\n\"\n", + " \"Agent: Absolutely, Lisa. I've made note of the discrepancies, and I'll escalate this to our billing department \"\n", + " \"for investigation and correction. You should see the adjustments on your next statement.\\n\"\n", + " \"Customer: That sounds good, Megan. I appreciate your help.\\n\"\n", + " \"Agent: Not a problem, Lisa. Have a wonderful day, and we'll get this sorted out for you.\\n\"\n", + ")\n", + "\n", + "DEMO_ANSWERS = (\n", + " \"1. The customer, contacted the call center regarding billing discrepancies on her statement. The agent, \"\n", + " \"acknowledged the issue, assured The customer it would be resolved, and escalated it to the billing department for \"\n", + " \"correction.\\n\"\n", + " \"2. Yes.\\n\"" + ] + }, + { + "cell_type": "markdown", + "id": "8b44ded3-fee3-4911-a02a-6a51a62a7020", + "metadata": {}, + "source": [ + "Then we need to wrap it all nicely to be given to the model as a single prompt, this is done with a text wrapper, and a question wrapper.
    \n", + "both of them will be concatenated inside the function with the questions and passed to the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2108f5aa-75a6-402d-83a6-bf45f0d7223a", + "metadata": {}, + "outputs": [], + "source": [ + "# The wrappers are built according to the model's convensions to improve result\n", + "TEXT_WRAPPER = (\n", + " f\"<|im_start|>system: You are an AI assistant that answers questions accurately and shortly<|im_end|>\\n\"\n", + " f\"<|im_start|>user: Given the following text:\\n\"\n", + " f\"{DEMO_CALL}\\n\"\n", + " f\"answer the questions as accurately as you can:\\n\"\n", + " f\"{QUESTIONS}<|im_end|>\\n\"\n", + " f\"<|im_start|>assistant:\\n\"\n", + " f\"{DEMO_ANSWERS}<|im_end|>\\n\"\n", + " f\"<|im_start|>user: Given the following text:\\n\"\n", + " \"{}\"\n", + ") \n", + "QUESTIONS_WRAPPER = (\n", + " \" answer the given questions as accurately as you can, do not write more answers the questions:\\n\"\n", + " \"{}<|im_end|>\\n\"\n", + " \"<|im_start|>assistant:\\n\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1a44b391-87d2-447d-aafa-66ed45f06ba5", + "metadata": {}, + "source": [ + "The last few parameters we need to set are the model we will use, the input lenth (no available for all models) and the batch size.
    \n", + "The batch size determains how many files we want procced at each epoch, and the larger we go the faster the proccess will be, as long as our memory is sufficient. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "528cae4c-541b-49a3-b24d-deb94f7130fb", + "metadata": {}, + "outputs": [], + "source": [ + "# We like this version of mistral's model, which is small and fast but also gives great results\n", + "qa_model = \"TheBloke/Mistral-7B-OpenOrca-GPTQ\"" + ] + }, + { + "cell_type": "markdown", + "id": "47fa4eaa-f3b0-457f-b98a-18a8ee5ba4d8", + "metadata": {}, + "source": [ + "Finnaly, we run the function with all the parameters we prepared. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d200706-e852-4ce9-9b9a-61686b30e5b7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Question answering:\n", + "demo2_run = func.run(\n", + " function=\"question-answering\",\n", + " local=True,\n", + " handler=\"answer_questions\",\n", + " inputs={\"data_path\": os.path.abspath(\"./calls\")},\n", + " params={\n", + " \"model_name\": qa_model,\n", + " \"device_map\": \"auto\",\n", + " \"text_wrapper\":TEXT_WRAPPER,\n", + " \"questions\": QUESTIONS,\n", + " \"questions_wrapper\": QUESTIONS_WRAPPER,\n", + " \"questions_columns\": qa_questions_columns,\n", + " },\n", + " returns=[\n", + " \"question_answering_df: dataset\",\n", + " \"question_answering_errors: result\",\n", + " ],\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "0d505915-49b5-47fb-9f50-ce15fe6dc392", + "metadata": {}, + "source": [ + "### (3.) Review results\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa39c5bf-c959-4ff5-ad60-4ad68d00f22c", + "metadata": {}, + "outputs": [], + "source": [ + "demo2_run.outputs" + ] + }, + { + "cell_type": "markdown", + "id": "947d6ce8-b330-44ab-b13f-b6eec20e839e", + "metadata": {}, + "source": [ + "## Demo 3" + ] + }, + { + "cell_type": "markdown", + "id": "66b916d2-96b0-448d-8e51-b51fb5a5a1a7", + "metadata": {}, + "source": [ + "This is also a large example, in this case we use another option of the function to ask questions in the form of a poll." + ] + }, + { + "cell_type": "markdown", + "id": "9ec66fc7-f50b-4417-a7cc-3c42848b1f01", + "metadata": {}, + "source": [ + "### (1.) Import the function (import mlrun, set project and import function)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "dfcab8d0-5022-40e5-92ff-14b02cfa2eaa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-18 10:18:37,490 [warning] Client version with higher version than server version isn't supported, align your client to the server version: {'parsed_server_version': Version(major=1, minor=5, patch=2, prerelease='rc1', build='track'), 'parsed_client_version': Version(major=1, minor=6, patch=0, prerelease='rc11', build=None)}\n" + ] + } + ], + "source": [ + "import os\n", + "import mlrun\n", + "import torch\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "49bc523b-9bca-46c5-917d-320d5641506a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-18 10:18:51,651 [info] Project loaded successfully: {'project_name': 'call-center-demo-zeev55'}\n" + ] + } + ], + "source": [ + "project = mlrun.get_or_create_project(\n", + " name=\"call-center-demo-3\",\n", + " context=\"./\",\n", + " user_project=True,\n", + " parameters={\n", + " \"default_image\": \"mlrun/mlrun\",\n", + " })\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "370f3780-0dfc-4b9c-87aa-1dd124e62249", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "func = project.set_function(\n", + " \"question-answering.py\",\n", + " name=\"question-answering\",\n", + " kind=\"job\",\n", + " handler=\"answer_questions\",\n", + ")\n", + "project.save()" + ] + }, + { + "cell_type": "markdown", + "id": "88dbe941-b9af-40bb-a038-7fcc812d506c", + "metadata": {}, + "source": [ + "### (2.) Usage\n", + "\n", + "Like in the second demo, we make a list of questions for the function to answer." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f9b02aaa-2a31-4ade-ba26-a2d73c5d03ab", + "metadata": {}, + "outputs": [], + "source": [ + "# These questions are harder to answer, as there is no right answer.\n", + "# So we want it to be at least consistent, for that we use the poll option.\n", + "QUESTIONS = [\n", + " \"1. Rate the agent's level of empathy (The ability to understand and share the feelings of others) on a scale of 1-5.\",\n", + " \"2. Rate the agent's level of professionalism (Conducting oneself in a way that is appropriate for the workplace) on a scale of 1-5.\",\n", + "]\n", + "\n", + "qa_questions_columns = [\n", + " \"empathy\",\n", + " \"professionalism\",\n", + "\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "id": "6ed8a0e3-9c5d-4524-bbe1-b345b981694a", + "metadata": {}, + "source": [ + "Another thing we give the model this time is answer examples (one/few shot answering), this can be done to show the model how you want the answer to be structured or caculated.
    \n", + "So for every file we ask about, the model will be presented with this example of a call and how we want the answers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d14e79d6-687c-4424-a01f-68376ad3dd30", + "metadata": {}, + "outputs": [], + "source": [ + "# For every file we ask about, the model will be presented with this example of a call and how we want the answers.\n", + "DEMO_CALL = (\n", + " \"Agent: Good afternoon, you've reached [Internet Service Provider] customer support. I'm Megan. How can I assist \"\n", + " \"you today?\\n\"\n", + " \"Customer: Hello, Megan. This is Lisa. I've noticed some billing discrepancies on my last statement.\\n\"\n", + " \"Agent: Thank you, Lisa. Let me pull up your account. I see the billing discrepancies you mentioned. It appears \"\n", + " \"there was an error in the charges. I apologize for the inconvenience.\\n\"\n", + " \"Customer: Thank you for acknowledging the issue, Megan. Can you please help me get it resolved?\\n\"\n", + " \"Agent: Absolutely, Lisa. I've made note of the discrepancies, and I'll escalate this to our billing department \"\n", + " \"for investigation and correction. You should see the adjustments on your next statement.\\n\"\n", + " \"Customer: That sounds good, Megan. I appreciate your help.\\n\"\n", + " \"Agent: Not a problem, Lisa. Have a wonderful day, and we'll get this sorted out for you.\\n\"\n", + ")\n", + "\n", + "\n", + "DEMO_ANSWERS = (\n", + " \"1. 4\\n\"\n", + " \"2. 5\\n\"\n", + "\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "86099fb8-895c-4e2c-979d-6bda9782ccd3", + "metadata": {}, + "source": [ + "Then we need to wrap it all nicely to be given to the model as a single prompt, this is done with a text wrapper, and a question wrapper.
    \n", + "both of them will be concatenated inside the function with the questions and passed to the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5efac70-cd2c-4fc7-bc9c-4c04d18077a1", + "metadata": {}, + "outputs": [], + "source": [ + "TEXT_WRAPPER = (\n", + " f\"<|im_start|>system: You are an AI assistant that answers questions accurately and shortly<|im_end|>\\n\"\n", + " f\"<|im_start|>user: Given the following text:\\n\"\n", + " f\"{DEMO_CALL}\\n\"\n", + " f\"answer the questions as accurately as you can:\\n\"\n", + " f\"{QUESTIONS}<|im_end|>\\n\"\n", + " f\"<|im_start|>assistant:\\n\"\n", + " f\"{DEMO_ANSWERS}<|im_end|>\\n\"\n", + " f\"<|im_start|>user: Given the following text:\\n\"\n", + " \"{}\"\n", + ") \n", + "\n", + "QUESTIONS_WRAPPER = (\n", + " \" answer the given questions as accurately as you can, do not write more answers the questions:\\n\"\n", + " \"{}<|im_end|>\\n\"\n", + " \"<|im_start|>assistant:\\n\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9339816e-d436-4add-b8f3-b48e577f4bfe", + "metadata": {}, + "source": [ + "The config is for the second questioning method, we cal \"poll\", and in which we need to choose how many voting models we want participating,
    \n", + "and in what way we want do decide the result, we currentlly support `average` and `most_common` as show here.
    \n", + "\n", + "\n", + "*An explenation about both questioning methods can be found in the begginig of this notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6330db65-9806-44a6-8046-0b156d2a3228", + "metadata": {}, + "outputs": [], + "source": [ + "questions_config = \n", + " {\n", + " \"type\": \"poll\",\n", + " \"poll_count\": 3, # How many 'voters'\n", + " \"poll_strategy\": \"most_common\"\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eaa0ae3d-9302-4b73-92f1-8c43ec92e9cd", + "metadata": {}, + "outputs": [], + "source": [ + "qa_model = \"TheBloke/Mistral-7B-OpenOrca-GPTQ\"" + ] + }, + { + "cell_type": "markdown", + "id": "20c0e1eb-49cf-426e-b125-eb133d440fbd", + "metadata": {}, + "source": [ + "Finnaly, we run the function with all the parameters we prepared. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03d6d619-618a-49d6-b0be-43c300902927", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Question answering:\n", + "demo3_run = func.run(\n", + " function=\"question-answering\",\n", + " local=True,\n", + " handler=\"answer_questions\",\n", + " inputs={\"data_path\": os.path.abspath(\"./calls\")},\n", + " params={\n", + " \"model_name\": qa_model,\n", + " \"device_map\": \"auto\",\n", + " \"text_wrapper\":TEXT_WRAPPER,\n", + " \"questions\": QUESTIONS,\n", + " \"questions_wrapper\": QUESTIONS_WRAPPER,\n", + " \"questions_columns\": qa_questions_columns,\n", + " \"questions_config\": questions_config, # This time we add 'questions_config'\n", + " },\n", + " returns=[\n", + " \"question_answering_df: dataset\",\n", + " \"question_answering_errors: result\",\n", + " ],\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "534edd4e-1e5b-4663-a2bb-bc6da7b603ca", + "metadata": {}, + "source": [ + "### (3.) Review results\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a61f06ad-ee28-45c9-b7da-d93c5a296810", + "metadata": {}, + "outputs": [], + "source": [ + "demo3_run.outputs" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/question_answering/0.5.0/src/question_answering.py b/functions/master/question_answering/0.5.0/src/question_answering.py new file mode 100644 index 00000000..2e4e96d0 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/question_answering.py @@ -0,0 +1,736 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum +import logging +import operator +import pathlib +from collections import Counter +from functools import reduce, wraps +from typing import Any, Dict, List, Tuple, Union + +import pandas as pd +import transformers +from tqdm import tqdm + +# Get the global logger: +_LOGGER = logging.getLogger() + + +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]: + global _LOGGER + + is_mpi = False + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + _LOGGER = context.logger + is_mpi = context.labels.get("kind", "job") == "mpijob" + + if is_mpi: + try: + from mpi4py import MPI + + return context, MPI.COMM_WORLD + except ModuleNotFoundError as mpi4py_not_found: + context.logger.error( + "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your " + "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi." + ) + raise mpi4py_not_found + except ModuleNotFoundError as module_not_found: + if is_mpi: + raise module_not_found + return None, None + + +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_text_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + dataframe = pd.concat(objs=[df for df, _ in output], axis=0) + errors_dictionary = reduce(operator.ior, [err for _, err in output], {}) + return dataframe, errors_dictionary + return None + + return wrapper + + return decorator + + +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def answer_questions( + data_path: Union[str, List[str]], + model_name: str, + questions: Union[List[str], List[List[str]]], + device_map: Union[str, dict] = None, + model_kwargs: dict = None, + auto_gptq_exllama_max_input_length: int = None, + tokenizer_name: str = None, + tokenizer_kwargs: dict = None, + text_wrapper: Union[str, List[str]] = "", + questions_wrapper: Union[str, List[str]] = "", + generation_config: Union[Dict, List[Dict]] = None, + questions_config: Union[Dict, List[Dict]] = None, + batch_size: int = 1, + questions_columns: List[str] = None, + verbose: bool = False, +) -> Tuple[pd.DataFrame, dict]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model. Each text file will have + the following prompt built: + + start of `text_wrapper` + + end of `text_wrapper` + + start of `questions_wrapper` + 1. + 2. + ... + n. + end of `questions_wrapper` + + :param data_path: A path to a directory of text files or a path to a text file to ask + questions about. + :param model_name: The pre-trained model name from the huggingface hub to use for asking + questions. + :param questions: The questions to ask. + A list of lists of questions to ask per text file, and devided + by question groups, the groups can be dtermained by size (in order to + avoid large inputs to the llm) or by questioning method + (regular or poll like questioning). + :param device_map: A map to use for loading the model on multiple devices. + :param model_kwargs: Keyword arguments to pass for loading the model using HuggingFace's + `transformers.AutoModelForCausalLM.from_pretrained` function. + :param auto_gptq_exllama_max_input_length: For AutoGPTQ models to set and extend the model's input buffer size. + :param tokenizer_name: The tokenizer name from the huggingface hub to use. If not given, the + model name will be used. + :param tokenizer_kwargs: Keyword arguments to pass for loading the tokenizer using HuggingFace's + `transformers.AutoTokenizer.from_pretrained` function. + :param text_wrapper: A wrapper for the file's text. Will be added at the start of the prompt. + Must have a placeholder ('{}') for the text of the file. + :param questions_wrapper: A wrapper for the questions received. Will be added after the text + wrapper in the prompt template. Must have a placeholder ('{}') for the + questions. + :param generation_config: HuggingFace's `GenerationConfig` keyword arguments to pass to the + `generate` method. + :param questions_config: A dictionary or list of dictionaries containing specific ways to answer + questions (using a poll for example), each dictionary in the list is for + corresponding question group and determines the question asking method + for said group. + :param batch_size: Batch size for inference. + :param questions_columns: Columns to use for the dataframe returned. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + + :returns: A tuple of: + + * A dataframe dataset of the questions answers. + * A dictionary of errored files that were not inferred or were not answered properly. + """ + global _LOGGER + + # Set configs to empty dict if not given: + if generation_config is None: + generation_config = {} + if questions_config is None: + questions_config = {} + + # Get the input text files to question: + if verbose: + _LOGGER.info("Collecting text files.") + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + text_files = _get_text_files(data_path=data_path) + else: + text_files = data_path + if verbose: + _LOGGER.info(f"Collected {len(text_files)} text files.") + + # Get the prompt template: + if verbose: + _LOGGER.info("Creating prompt template.") + + # Organize questions as a list of list, and count number of sub-lists for future use + number_of_question_groups = 1 if isinstance(questions[0], str) else len(questions) + questions = _to_group_list( + argument_value=questions, + argument_name="questions", + length=number_of_question_groups, + ) + + # Organize prompt parts at proper length + text_wrapper = _to_group_list( + argument_value=text_wrapper, + argument_name="text_wrapper", + length=number_of_question_groups, + ) + questions_wrapper = _to_group_list( + argument_value=questions_wrapper, + argument_name="questions_wrapper", + length=number_of_question_groups, + ) + + # Create a list of prompt according to given parts and questions + prompt_template = [] + questions = questions if isinstance(questions[0], list) else [questions] + + # Build all prompts + for i in range(number_of_question_groups): + prompt_template.append( + _get_prompt_template( + text_wrapper=text_wrapper[i], + questions_wrapper=questions_wrapper[i], + questions=questions[i], + ) + ) + if verbose: + _LOGGER.info(f"Prompt template created:\n\n{prompt_template}\n") + + # Get the total amount of questions: + questions_amount = sum([len(sublist) for sublist in questions]) + + # Get the questions columns: + questions_columns = questions_columns or [ + f"q{i}" for i in range(1, questions_amount + 1) + ] + + # Check if we have the correct amount of questions columns: + if len(questions_columns) != questions_amount: + raise ValueError( + f"The provided questions columns length ({len(questions_columns)}) " + f"does not match the questions amount ({questions_amount})" + ) + + # Load the generation config: + if verbose: + _LOGGER.info("Loading generation configuration.") + generation_config = [ + transformers.GenerationConfig(**(cfg or {})) + for cfg in _to_group_list( + argument_value=generation_config, + argument_name="generation_config", + length=number_of_question_groups, + ) + ] + if verbose: + _LOGGER.info(f"Generation configuration loaded: {generation_config}") + + # Load the model and tokenizer into a pipeline object: + if verbose: + _LOGGER.info(f"Loading model '{model_name}'.") + generation_pipeline = _get_generation_pipeline( + model_name=model_name, + device_map=device_map, + tokenizer_name=tokenizer_name or model_name, + model_kwargs=model_kwargs or {}, + tokenizer_kwargs=tokenizer_kwargs or {}, + auto_gptq_exllama_max_input_length=auto_gptq_exllama_max_input_length, + batch_size=batch_size, + ) + if verbose: + _LOGGER.info("Model loaded.") + + # Prepare the successes dataframe and errors dictionary to be returned: + successes = [] + errors = {} + + # Split the files into batches: + file_batches = [ + text_files[i : i + batch_size] + if i + batch_size < len(text_files) + else text_files[i:] + for i in range(0, len(text_files), batch_size) + ] + questions_config = _to_group_list( + argument_value=questions_config, + argument_name="questions_config", + length=number_of_question_groups, + ) + + # Create a list of question handlers according to given configs + handlers = [] + for cfg in questions_config: + question_type = cfg.pop("type", "default") + handlers.append(QUESTION_MAPPING.get(question_type)(**cfg)) + + # Go over the batches of text files and question them: + for file_batch in tqdm( + file_batches, + desc="Generating answers", + unit=f"file (batch of {batch_size})", + disable=not verbose, + ): + try: + total_answers = [[] for _ in range(batch_size)] + + # Go over all question group per batch of documents + for question_group in range(number_of_question_groups): + current_questions_amount = len(questions[question_group]) + + # Read batch (read the text from the text files): + batched_input = _read_file_batch( + file_batch=file_batch, + prompt_template=prompt_template[question_group], + ) + + # Answer the questions with each question handler: + batched_answers = handlers[question_group].answer( + questions_amount=current_questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config[question_group], + ) + + # Put the answers in the correct place in the total answers list according to the place in the batch: + for i in range(batch_size): + total_answers[i].extend(batched_answers[i]) + + # Collect the answers and attach the file name: + successes.extend( + [ + [file.name, *answers] + for file, answers in zip(file_batch, total_answers) + ] + ) + except Exception as exception: + # Note the exception as error in the dictionary: + batch_file_names = ", ".join([file.name for file in file_batch]) + if verbose: + _LOGGER.warning( + f"Error in batch '{batch_file_names}': {str(exception)}" + ) + errors[batch_file_names] = str(exception) + continue + + # Construct the answers dataframe: + columns = [ + "text_file", + *questions_columns, + ] + + # Create a data frame of answers by files + successes = pd.DataFrame( + successes, + columns=columns, + ) + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(text_files)})\n" + f"Answers summary:\n" + f"{successes.head()}" + ) + return successes, errors + + +def _get_text_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + + # Get all files inside the directory: + text_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + text_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return text_files + + +def _get_prompt_template( + text_wrapper: str, + questions_wrapper: str, + questions: List[str], +) -> str: + + # Validate and build the text wrapper: + text_wrapper = text_wrapper or ( + "Given the following text:\n" "-----\n" "{}\n" "-----" + ) + if text_wrapper.count("{}") != 1: + raise ValueError( + "The `text_wrapper` must include one placeholder '{}' for the text of the file to be asked about." + ) + + # Validate and build the question wrapper: + questions_wrapper = questions_wrapper or "Answer the questions:\n" "{}" + if questions_wrapper.count("{}") != 1: + raise ValueError( + "The `questions_wrapper` must include one placeholder '{}' for the list of questions." + ) + + # Validate and parse the questions: + if len(questions) == 0: + raise ValueError("Please include at least one question.") + questions = "\n".join( + [f"{i}. {question}" for i, question in enumerate(questions, 1)] + ) + + # Construct the template: + return f"{text_wrapper}\n{questions_wrapper.format(questions)}\n" + + +def _get_generation_pipeline( + model_name: str, + device_map: Union[str, dict], + tokenizer_name: str, + model_kwargs: dict, + tokenizer_kwargs: dict, + auto_gptq_exllama_max_input_length: int = None, + batch_size: int = 1, +): + # Load the model: + model = transformers.AutoModelForCausalLM.from_pretrained( + model_name, device_map=device_map, **model_kwargs + ) + + # Set exllama max input length if provided: + # This changes the model's context size. + if auto_gptq_exllama_max_input_length: + from auto_gptq import exllama_set_max_input_length + + model = exllama_set_max_input_length( + model=model, max_input_length=auto_gptq_exllama_max_input_length + ) + + # Load the tokenizer: + tokenizer = transformers.AutoTokenizer.from_pretrained( + tokenizer_name, **tokenizer_kwargs + ) + + # Initialize a generation pipline and return: + pipe = transformers.pipeline( + task="text-generation", + model=model, + tokenizer=tokenizer, + batch_size=batch_size, + ) + pipe.tokenizer.pad_token_id = model.config.eos_token_id + return pipe + + +def _read_file_batch( + file_batch: List[pathlib.Path], + prompt_template: str, +) -> List[str]: + batch = [] + + # Go over all files and read in usable format + for file in file_batch: + with open(file, "r", encoding="utf-8") as fp: + batch.append(prompt_template.format(fp.read())) + return batch + + +def _to_group_list(argument_value: list, argument_name: str, length: int): + + # Check if is list, turn to list if not + argument_value = ( + argument_value if isinstance(argument_value, list) else [argument_value] + ) + list_len = len(argument_value) + + # If not a list, or is a list of len 1 we duplicate for correct length + # If list in wrong length throw an error + if list_len != length: + if list_len == 1: + return argument_value * length + raise ValueError( + f"The argument value of '{argument_name}' is not equal to the length of the given questions - {length}" + ) + return argument_value + + +class QuestionHandler: + """ + A class for handling questions answering for a given question type. + This class is used as a base class for all question types, and for default question type (regular question + answering without any special handling). + """ + + class ConfigKeys: + pass + + def __init__(self): + pass + + @staticmethod + def _get_answers(generated_text: str, questions_amount: int) -> List[str]: + + # Clear answer start (part before numbers): + # TODO find better way to verify, for list of questions this is redundant for example + if "1." not in generated_text: + raise ValueError( + f"Answer 1. is missing from the generated text: '{generated_text}'" + ) + text = generated_text.split("1.", 1)[1] + + # Start extracting the answers: + answers = [] + for i in range(1, questions_amount + 1): + # If it's the last answer to look for, take the rest of the text: + if i == questions_amount: + answer_i = text + # Verify there is a question number in the text: + elif f"{i + 1}." not in text: + raise ValueError( + f"Answer {i + 1}. is missing from the generated text: '{generated_text}'" + ) + # Take i's answer: + else: + answer_i, text = text.split(f"{i + 1}.", 1) + # Collect the answer removing redundant spaces: + answers.append(answer_i.strip()) + + return answers + + def _infer_questions( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + + # Infer through the llm: + batched_output = generation_pipeline( + batched_input, + generation_config=generation_config, + eos_token_id=generation_pipeline.tokenizer.eos_token_id, + return_full_text=False, + num_return_sequences=1, + ) + + # Process the outputs to get the answers: + batched_answers = [] + for output in batched_output: + # Get the generated answers: + answers = self._get_answers( + generated_text=output[0]["generated_text"], + questions_amount=questions_amount, + ) + # Collect the processed answers: + batched_answers.append(answers) + return batched_answers + + def answer( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline. + """ + return self._infer_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + ) + + +class PollQuestionHandler(QuestionHandler): + """ + Static class to hold all the possible poll question configurations options keys + """ + + class ConfigKeys: + """ + A class for handling questions answering for poll type questions. + These type of question are answered by asking the same question multiple times + and choosing the most common answer or the average answer. + """ + + #: The number of times to ask the same question. + POLL_COUNT = "poll_count" + + #: The strategy to use for choosing the answer from the poll. + POLL_STRATEGY = "poll_strategy" + + class Strategy(enum.Enum): + #: The most common answer strategy. + MOST_COMMON = "most_common" + + #: The average answer strategy. + AVERAGE = "average" + + @staticmethod + def most_common(answers): + """ + Calculate the most common answer for a given list of answers. + """ + count = Counter(answers) + most_common = count.most_common(1) + return most_common[0][0] + + @staticmethod + def average(answers): + """ + Calculate the average answer for a given list of answers. + """ + if isinstance(answers[0], str): + raise ValueError( + "Cannot perform poll with average answer strategy of non numeric values," + " please change the question to give numeric data, or choose 'most_common' as strategy." + ) + else: + numeric_values = answers + avg = sum(numeric_values) / len(numeric_values) + + # Round to the closest integer and return corresponding value + return round(avg) + + def do(self, answers): + """ + Perform the strategy. + """ + return getattr(self, self.value)(answers) + + def __init__( + self, poll_count: int = 5, poll_strategy: str = "most_common"): + super().__init__() + self.poll_count = poll_count + self.poll_strategy = self.Strategy(poll_strategy) + + def answer( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline. + """ + return self._answer_poll_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + ) + + def _answer_poll_questions( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + votes = [] + + # Run the poll for each question + for _ in range(self.poll_count): + batched_answers = self._infer_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + ) + votes.append(batched_answers) + answers = [] + + # Collect the answers according to the poll strategy + # Average strategy works for numeric values only + for batch in range(len(votes[0])): + batched_answers = [] + for question in range(questions_amount): + # Create a list of all answers to relevant question + answer = [ + votes[voter][batch][question] for voter in range(self.poll_count) + ] + answer = self.poll_strategy.do(answer) + batched_answers.append(answer) + answers.append(batched_answers) + return answers + + +# Holds names of QuestionHandles +class QuestionTypes: + DEFAULT = "default" + POLL = "poll" + + +# Maps question types to their handlers +QUESTION_MAPPING = { + QuestionTypes.DEFAULT: QuestionHandler, + QuestionTypes.POLL: PollQuestionHandler, +} diff --git a/functions/master/question_answering/0.5.0/src/requirements.txt b/functions/master/question_answering/0.5.0/src/requirements.txt new file mode 100644 index 00000000..d05cb777 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/requirements.txt @@ -0,0 +1,4 @@ +transformers +tqdm +torch +einops \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/src/test_question_answering.py b/functions/master/question_answering/0.5.0/src/test_question_answering.py new file mode 100644 index 00000000..f35b4364 --- /dev/null +++ b/functions/master/question_answering/0.5.0/src/test_question_answering.py @@ -0,0 +1,76 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import mlrun +import transformers +import tempfile + +APPLE_COLOR = "red" + + +def mock_pipeline_call(*args, **kwargs): + return [[{"generated_text": "1. " + APPLE_COLOR}]] + + +def _make_data_dir_for_test(): + data_dir = tempfile.mkdtemp() + content = "The apple color is red." + with open(data_dir + "/test_data.txt", "w") as f: + f.write(content) + return data_dir + + +def test_question_answering(monkeypatch): + monkeypatch.setattr(transformers.Pipeline, "__call__", mock_pipeline_call) + input_path = "./data" + artifact_path = tempfile.mkdtemp() + project = mlrun.new_project("qa", context="./") + fn = project.set_function("question_answering.py", "answer_questions", kind="job", image="mlrun/mlrun") + qa_run = fn.run( + handler="answer_questions", + params={ + "model_name": "distilgpt2", + "data_path": input_path, + "text_wrapper": ( + "Given the following sentence:\n" + "-----\n" + "{}\n" + "-----" + ), + "questions": [ + "What is the color of the apple?", + ], + "questions_columns": [ + "color", + ], + "generation_config": { + "do_sample": True, + "temperature": 0.8, + "top_p": 0.9, + "early_stopping": True, + "max_new_tokens": 20, + }, + }, + returns=[ + "question_answering_df: dataset", + "question_answering_errors: result", + ], + local=True, + artifact_path=artifact_path + ) + qa_df = mlrun.get_dataitem( + qa_run.status.artifacts[0]["spec"]["target_path"] + ).as_df() + assert qa_df["color"][0] == APPLE_COLOR + assert qa_run.outputs["question_answering_errors"] == {} diff --git a/functions/master/question_answering/0.5.0/static/documentation.html b/functions/master/question_answering/0.5.0/static/documentation.html new file mode 100644 index 00000000..602f654b --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/documentation.html @@ -0,0 +1,447 @@ + + + + + + + +question_answering package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    question_answering package#

    +
    +

    Submodules#

    +
    +
    +

    question_answering.question_answering module#

    +
    +
    +class question_answering.question_answering.PollQuestionHandler(poll_count: int = 5, poll_strategy: str = 'most_common')[source]#
    +

    Bases: QuestionHandler

    +

    Static class to hold all the possible poll question configurations options keys

    +
    +
    +class ConfigKeys[source]#
    +

    Bases: object

    +

    A class for handling questions answering for poll type questions. +These type of question are answered by asking the same question multiple times +and choosing the most common answer or the average answer.

    +
    +
    +POLL_COUNT = 'poll_count'#
    +

    The number of times to ask the same question.

    +
    +
    +
    +POLL_STRATEGY = 'poll_strategy'#
    +

    The strategy to use for choosing the answer from the poll.

    +
    +
    +
    +
    +class Strategy(value)[source]#
    +

    Bases: Enum

    +

    An enumeration.

    +
    +
    +AVERAGE = 'average'#
    +

    The average answer strategy.

    +
    +
    +
    +MOST_COMMON = 'most_common'#
    +

    The most common answer strategy.

    +
    +
    +
    +static average(answers)[source]#
    +

    Calculate the average answer for a given list of answers.

    +
    +
    +
    +do(answers)[source]#
    +

    Perform the strategy.

    +
    +
    +
    +static most_common(answers)[source]#
    +

    Calculate the most common answer for a given list of answers.

    +
    +
    +
    +
    +answer(questions_amount: int, batched_input: List[str], generation_pipeline: transformers.Pipeline, generation_config: transformers.GenerationConfig) List[List[str]][source]#
    +

    Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline.

    +
    +
    +
    +
    +class question_answering.question_answering.QuestionHandler[source]#
    +

    Bases: object

    +

    A class for handling questions answering for a given question type. +This class is used as a base class for all question types, and for default question type (regular question +answering without any special handling).

    +
    +
    +class ConfigKeys[source]#
    +

    Bases: object

    +
    +
    +
    +answer(questions_amount: int, batched_input: List[str], generation_pipeline: transformers.Pipeline, generation_config: transformers.GenerationConfig) List[List[str]][source]#
    +

    Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline.

    +
    +
    +
    +
    +class question_answering.question_answering.QuestionTypes[source]#
    +

    Bases: object

    +
    +
    +DEFAULT = 'default'#
    +
    +
    +
    +POLL = 'poll'#
    +
    +
    +
    +
    +question_answering.question_answering.answer_questions(data_path: str | List[str], model_name: str, questions: List[str] | List[List[str]], device_map: str | dict | None = None, model_kwargs: dict | None = None, auto_gptq_exllama_max_input_length: int | None = None, tokenizer_name: str | None = None, tokenizer_kwargs: dict | None = None, text_wrapper: str | List[str] = '', questions_wrapper: str | List[str] = '', generation_config: Dict | List[Dict] | None = None, questions_config: Dict | List[Dict] | None = None, batch_size: int = 1, questions_columns: List[str] | None = None, verbose: bool = False) Tuple[DataFrame, dict][source]#
    +

    Answer questions with a context to the given text files contents by a pretrained LLM model. Each text file will have +the following prompt built:

    +

    start of text_wrapper +<text file content> +end of text_wrapper

    +

    start of questions_wrapper +1. <questions[0]> +2. <questions[1]> +… +n. <questions[n-1]> +end of questions_wrapper

    +
    +
    Parameters:
    +
      +
    • data_path – A path to a directory of text files or a path to a text file to ask +questions about.

    • +
    • model_name – The pre-trained model name from the huggingface hub to use for asking +questions.

    • +
    • questions – The questions to ask. +A list of lists of questions to ask per text file, and devided +by question groups, the groups can be dtermained by size (in order to +avoid large inputs to the llm) or by questioning method +(regular or poll like questioning).

    • +
    • device_map – A map to use for loading the model on multiple devices.

    • +
    • model_kwargs – Keyword arguments to pass for loading the model using HuggingFace’s +transformers.AutoModelForCausalLM.from_pretrained function.

    • +
    • auto_gptq_exllama_max_input_length – For AutoGPTQ models to set and extend the model’s input buffer size.

    • +
    • tokenizer_name – The tokenizer name from the huggingface hub to use. If not given, the +model name will be used.

    • +
    • tokenizer_kwargs – Keyword arguments to pass for loading the tokenizer using HuggingFace’s +transformers.AutoTokenizer.from_pretrained function.

    • +
    • text_wrapper – A wrapper for the file’s text. Will be added at the start of the prompt. +Must have a placeholder (‘{}’) for the text of the file.

    • +
    • questions_wrapper – A wrapper for the questions received. Will be added after the text +wrapper in the prompt template. Must have a placeholder (‘{}’) for the +questions.

    • +
    • generation_config – HuggingFace’s GenerationConfig keyword arguments to pass to the +generate method.

    • +
    • questions_config – A dictionary or list of dictionaries containing specific ways to answer +questions (using a poll for example), each dictionary in the list is for +corresponding question group and determines the question asking method +for said group.

    • +
    • batch_size – Batch size for inference.

    • +
    • questions_columns – Columns to use for the dataframe returned.

    • +
    • verbose – Whether to present logs of a progress bar and errors. Default: True.

    • +
    +
    +
    Returns:
    +

    A tuple of:

    +
      +
    • A dataframe dataset of the questions answers.

    • +
    • A dictionary of errored files that were not inferred or were not answered properly.

    • +
    +

    +
    +
    +
    +
    +
    +question_answering.question_answering.open_mpi_handler(worker_inputs: List[str], root_worker_inputs: Dict[str, Any] | None = None)[source]#
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/static/example.html b/functions/master/question_answering/0.5.0/static/example.html new file mode 100644 index 00000000..e54ca5e5 --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/example.html @@ -0,0 +1,798 @@ + + + + + + + +Question Answering + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    Question Answering#

    +
    +

    Short description and explenation#

    +

    This function enables ad-hoc question answering over documents by ingesting text into a language model and returning formatted responses.
    +It accepts:

    +
      +
    • A language model

    • +
    • Text files with content

    • +
    • Questions to answer

    • +
    • More inputs can be given for configuration

    • +
    +

    The model processes the files to build understanding. Questions posed are then answered in one of two modes:

    +

    Default mode:
    +The model directly answers each question using its own capabilities.

    +

    Poll mode:
    +Additional models are included to separately answer each question. An aggregation algorithm determines the best response through consensus between models.
    +Two options exist for consensus methodology:

    +

    Average Answer:
    +Each model’s answer is scored. The response with the average highest score amongst models is selected. Useful for numeric or ranked responses.

    +

    Most Common Answer:
    The answer that occurs most frequently across models is selected. Useful for textual responses to avoid outliers.

    +

    Using multiple models via the poll mode provides accuracy improvements for questions lacking definitive answers, as it refines responses through an ensemble process.

    +
    +
    +

    Background#

    +

    At the core, advanced natural language processing (NLP) models called foundation models are being leveraged to read and comprehend the input text files.
    +Specifically, models such as GPT-3 or Codex from Anthropic are used as the base language model.

    +

    When documents are fed into the function, the background process invokes these models to ingest and digest the information.

    +

    This provides the knowledge base for the models to then offer informed answers tailored to any queries about the documents.
    +The parameters controlling model size and computation time provide tradeoffs between cost, speed, and sophistication of comprehension.

    +

    Additionally, the poll option expands on a single model by sampling responses from a number of models as mentioned above.

    +
    +
    +

    Requirements#

    +

    transformers
    +torch
    +tqdm

    +
    +
    +

    Documentation#

    +

    data_path: A path to a directory of text files or a path to a text file to ask questions about.

    +

    model_name: The pre-trained model name from the huggingface hub to use for answering questions.

    +

    questions: The questions to ask. A list of lists of questions to ask per text file, and devided
    +by question groups, the groups can be determained by size (in order to
    +avoid large inputs to the llm) or by questioning method (regular or poll like questioning).

    +

    device_map: A map to use for loading the model on multiple devices.

    +

    model_kwargs: Keyword arguments to pass for loading the model using HuggingFace’s
    +transformers.AutoModelForCausalLM.from_pretrained function.

    +

    auto_gptq_exllama_max_input_length: For AutoGPTQ models to set and extend the model’s input buffer size.

    +

    tokenizer_name: The tokenizer name from the huggingface hub to use. If not given, the given model name will be used.

    +

    tokenizer_kwargs: Keyword arguments to pass for loading the tokenizer using HuggingFace’s
    +transformers.AutoTokenizer.from_pretrained function.

    +

    text_wrapper: Must have a placeholder (‘{}’) for the text of the file.

    +

    questions_wrapper: A wrapper for the questions received. Will be added after the text wrapper in the prompt template.
    +Must have a placeholder (‘{}’) for the questions.

    +

    generation_config: HuggingFace’s GenerationConfig keyword arguments to pass to the generate method.

    +

    questions_config: A dictionary or list of dictionaries containing specific ways to answer questions (using a poll for example),
    +each dictionary in the list is for corresponding question group and determines the question asking method
    +for said group.

    +

    batch_size: Batch size for inference.

    +

    questions_columns: Columns to use for the dataframe returned.

    +

    verbose: Whether to present logs of a progress bar and errors. Default: True.

    +
    +
    +

    Demo 1#

    +

    This is a short and simple example to show the basic use of the function.

    +
    +

    (1.) Import the function (import mlrun, set project and import function)#

    +
    +
    +
    import mlrun
    +import transformers
    +import tempfile
    +
    +
    +
    +
    +
    +
    +
    project = mlrun.get_or_create_project(
    +    name="call-center-demo-1",
    +    context="./",
    +    user_project=True,
    +    parameters={
    +        "default_image": "mlrun/mlrun",
    +    })
    +
    +
    +
    +
    +
    +
    +
    func = project.set_function(
    +    "question-answering.py",
    +    name="question-answering",
    +    kind="job",
    +    handler="answer_questions",
    +)
    +project.save()
    +
    +
    +
    +
    +

    We create a text file that the model can be asked about

    +
    +
    +
    def _make_data_dir_for_test():
    +    data_dir = tempfile.mkdtemp()
    +    # The information the model will need in order to answer our question
    +    content = "The apple is red."
    +    with open(data_dir + "/test_data.txt", "w") as f:
    +        f.write(content)
    +    return data_dir
    +
    +
    +
    +
    +
    +
    +

    (2.) Usage#

    +

    Then we set where to take the path to the text file we want to ask about, the questions, and column name for the answer table.

    +
    +
    +
    input_path = _make_data_dir_for_test()
    +# The question for the model to answer
    +question = ["What is the color of the apple?"]
    +# The column of the answer in the data frame returned by the function
    +column_name = ["color"]
    +
    +
    +
    +
    +

    Now we run the function with all the parameters we prepered earlier

    +
    +
    +
    demo1_run = func.run(
    +    handler="answer_questions",
    +    params={
    +        "model": "distilgpt2",
    +        "input_path": input_path,
    +        "questions": question,
    +        "questions_columns": column_name,
    +        "generation_config": {
    +            "do_sample": True,
    +            "temperature": 0.8,
    +            "top_p": 0.9,
    +            "early_stopping": True,
    +            "max_new_tokens": 20,
    +        },
    +    },
    +    returns=[
    +        "question_answering_df: dataset",
    +        "question_answering_errors: result",
    +    ],
    +    local=True,
    +    artifact_path="./"
    +)
    +
    +
    +
    +
    +
    +
    +

    (3.) Review results#

    +

    and after the run is finished we can take a look and see our answer

    +
    +
    +
    demo1_run.outputs
    +
    +
    +
    +
    +
    +
    +
    +

    Demo 2#

    +

    This is a much larger example, we will show how we use this function to analyze a number of calls between agents and customer of a internet company (all the data is generated by Iguazio).
    +For something like this, we recomend using a strong model, and putting some time into making the prompts.

    +
    +

    (1.) Import the function (import mlrun, set project and import function)#

    +
    +
    +
    import os
    +import mlrun
    +import torch
    +from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline
    +
    +
    +
    +
    +
    > 2023-12-18 10:18:37,490 [warning] Client version with higher version than server version isn't supported, align your client to the server version: {'parsed_server_version': Version(major=1, minor=5, patch=2, prerelease='rc1', build='track'), 'parsed_client_version': Version(major=1, minor=6, patch=0, prerelease='rc11', build=None)}
    +
    +
    +
    +
    +
    +
    +
    project = mlrun.get_or_create_project(
    +    name="call-center-demo-2",
    +    context="./",
    +    user_project=True,
    +    parameters={
    +        "default_image": "mlrun/mlrun",
    +    })
    +
    +
    +
    +
    +
    > 2023-12-18 10:18:51,651 [info] Project loaded successfully: {'project_name': 'call-center-demo-zeev55'}
    +
    +
    +
    +
    +
    +
    +
    func = project.set_function(
    +    "question-answering.py",
    +    name="question-answering",
    +    kind="job",
    +    handler="answer_questions",
    +)
    +project.save()
    +
    +
    +
    +
    +
    <mlrun.projects.project.MlrunProject at 0x7f8bc5b0a370>
    +
    +
    +
    +
    +
    +
    +

    (2.) Usage#

    +

    This example is a bit more complicated as we mentioned, we give the model a list of questions, for some of them we give the model a list of answers to choose from.

    +
    +
    +
    QUESTIONS = [
    +    "1. Write a long summary of the text, focus on the topic (max 50 words).",
    +    "2. Was the Client's concern addressed, (choose only one) [Yes, No]?",
    +    ]
    +
    +qa_questions_columns = [
    +                        "Summary",
    +                        "is_fixed",
    +                        ]
    +
    +
    +
    +
    +

    Another thing we give the model this time is answer examples (one/few shot answering), this can be done to show the model how you want the answer to be structured or caculated.

    +
    +
    +
    # For every file we ask about, the model will be presented with this example of a call and how we want the answers.
    +DEMO_CALL = (
    +    "Agent: Good afternoon, you've reached [Internet Service Provider] customer support. I'm Megan. How can I assist "
    +    "you today?\n"
    +    "Customer: Hello, Megan. This is Lisa. I've noticed some billing discrepancies on my last statement.\n"
    +    "Agent: Thank you, Lisa. Let me pull up your account. I see the billing discrepancies you mentioned. It appears "
    +    "there was an error in the charges. I apologize for the inconvenience.\n"
    +    "Customer: Thank you for acknowledging the issue, Megan. Can you please help me get it resolved?\n"
    +    "Agent: Absolutely, Lisa. I've made note of the discrepancies, and I'll escalate this to our billing department "
    +    "for investigation and correction. You should see the adjustments on your next statement.\n"
    +    "Customer: That sounds good, Megan. I appreciate your help.\n"
    +    "Agent: Not a problem, Lisa. Have a wonderful day, and we'll get this sorted out for you.\n"
    +)
    +
    +DEMO_ANSWERS = (
    +    "1. The customer, contacted the call center regarding billing discrepancies on her statement. The agent, "
    +    "acknowledged the issue, assured The customer it would be resolved, and escalated it to the billing department for "
    +    "correction.\n"
    +    "2. Yes.\n"
    +
    +
    +
    +
    +

    Then we need to wrap it all nicely to be given to the model as a single prompt, this is done with a text wrapper, and a question wrapper.
    +both of them will be concatenated inside the function with the questions and passed to the model.

    +
    +
    +
    # The wrappers are built according to the model's convensions to improve result
    +TEXT_WRAPPER = (
    +    f"<|im_start|>system: You are an AI assistant that answers questions accurately and shortly<|im_end|>\n"
    +    f"<|im_start|>user: Given the following text:\n"
    +    f"{DEMO_CALL}\n"
    +    f"answer the questions as accurately as you can:\n"
    +    f"{QUESTIONS}<|im_end|>\n"
    +    f"<|im_start|>assistant:\n"
    +    f"{DEMO_ANSWERS}<|im_end|>\n"
    +    f"<|im_start|>user: Given the following text:\n"
    +    "{}"
    +) 
    +QUESTIONS_WRAPPER = (
    +    " answer the given questions as accurately as you can, do not write more answers the questions:\n"
    +    "{}<|im_end|>\n"
    +    "<|im_start|>assistant:\n"
    +)
    +
    +
    +
    +
    +

    The last few parameters we need to set are the model we will use, the input lenth (no available for all models) and the batch size.
    +The batch size determains how many files we want procced at each epoch, and the larger we go the faster the proccess will be, as long as our memory is sufficient.

    +
    +
    +
    # We like this version of mistral's model, which is small and fast but also gives great results
    +qa_model = "TheBloke/Mistral-7B-OpenOrca-GPTQ"
    +
    +
    +
    +
    +

    Finnaly, we run the function with all the parameters we prepared.

    +
    +
    +
    # Question answering:
    +demo2_run = func.run(
    +    function="question-answering",
    +    local=True,
    +    handler="answer_questions",
    +    inputs={"data_path": os.path.abspath("./calls")},
    +    params={
    +        "model_name": qa_model,
    +        "device_map": "auto",
    +        "text_wrapper":TEXT_WRAPPER,
    +        "questions": QUESTIONS,
    +        "questions_wrapper": QUESTIONS_WRAPPER,
    +        "questions_columns": qa_questions_columns,
    +    },
    +    returns=[
    +        "question_answering_df: dataset",
    +        "question_answering_errors: result",
    +    ],
    +)
    +
    +
    +
    +
    +
    +
    +

    (3.) Review results#

    +
    +
    +
    demo2_run.outputs
    +
    +
    +
    +
    +
    +
    +
    +

    Demo 3#

    +

    This is also a large example, in this case we use another option of the function to ask questions in the form of a poll.

    +
    +

    (1.) Import the function (import mlrun, set project and import function)#

    +
    +
    +
    import os
    +import mlrun
    +import torch
    +from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, pipeline
    +
    +
    +
    +
    +
    > 2023-12-18 10:18:37,490 [warning] Client version with higher version than server version isn't supported, align your client to the server version: {'parsed_server_version': Version(major=1, minor=5, patch=2, prerelease='rc1', build='track'), 'parsed_client_version': Version(major=1, minor=6, patch=0, prerelease='rc11', build=None)}
    +
    +
    +
    +
    +
    +
    +
    project = mlrun.get_or_create_project(
    +    name="call-center-demo-3",
    +    context="./",
    +    user_project=True,
    +    parameters={
    +        "default_image": "mlrun/mlrun",
    +    })
    +
    +
    +
    +
    +
    > 2023-12-18 10:18:51,651 [info] Project loaded successfully: {'project_name': 'call-center-demo-zeev55'}
    +
    +
    +
    +
    +
    +
    +
    func = project.set_function(
    +    "question-answering.py",
    +    name="question-answering",
    +    kind="job",
    +    handler="answer_questions",
    +)
    +project.save()
    +
    +
    +
    +
    +
    <mlrun.projects.project.MlrunProject at 0x7f8bc5b0a370>
    +
    +
    +
    +
    +
    +
    +

    (2.) Usage#

    +

    Like in the second demo, we make a list of questions for the function to answer.

    +
    +
    +
    # These questions are harder to answer, as there is no right answer.
    +# So we want it to be at least consistent, for that we use the poll option.
    +QUESTIONS = [
    +    "1. Rate the agent's level of empathy (The ability to understand and share the feelings of others) on a scale of 1-5.",
    +    "2. Rate the agent's level of professionalism (Conducting oneself in a way that is appropriate for the workplace) on a scale of 1-5.",
    +]
    +
    +qa_questions_columns = [
    +                        "empathy",
    +                        "professionalism",
    +
    +                        ]
    +
    +
    +
    +
    +

    Another thing we give the model this time is answer examples (one/few shot answering), this can be done to show the model how you want the answer to be structured or caculated.
    +So for every file we ask about, the model will be presented with this example of a call and how we want the answers.

    +
    +
    +
    # For every file we ask about, the model will be presented with this example of a call and how we want the answers.
    +DEMO_CALL = (
    +    "Agent: Good afternoon, you've reached [Internet Service Provider] customer support. I'm Megan. How can I assist "
    +    "you today?\n"
    +    "Customer: Hello, Megan. This is Lisa. I've noticed some billing discrepancies on my last statement.\n"
    +    "Agent: Thank you, Lisa. Let me pull up your account. I see the billing discrepancies you mentioned. It appears "
    +    "there was an error in the charges. I apologize for the inconvenience.\n"
    +    "Customer: Thank you for acknowledging the issue, Megan. Can you please help me get it resolved?\n"
    +    "Agent: Absolutely, Lisa. I've made note of the discrepancies, and I'll escalate this to our billing department "
    +    "for investigation and correction. You should see the adjustments on your next statement.\n"
    +    "Customer: That sounds good, Megan. I appreciate your help.\n"
    +    "Agent: Not a problem, Lisa. Have a wonderful day, and we'll get this sorted out for you.\n"
    +)
    +
    +
    +DEMO_ANSWERS = (
    +    "1. 4\n"
    +    "2. 5\n"
    +
    +)
    +
    +
    +
    +
    +

    Then we need to wrap it all nicely to be given to the model as a single prompt, this is done with a text wrapper, and a question wrapper.
    +both of them will be concatenated inside the function with the questions and passed to the model.

    +
    +
    +
    TEXT_WRAPPER = (
    +    f"<|im_start|>system: You are an AI assistant that answers questions accurately and shortly<|im_end|>\n"
    +    f"<|im_start|>user: Given the following text:\n"
    +    f"{DEMO_CALL}\n"
    +    f"answer the questions as accurately as you can:\n"
    +    f"{QUESTIONS}<|im_end|>\n"
    +    f"<|im_start|>assistant:\n"
    +    f"{DEMO_ANSWERS}<|im_end|>\n"
    +    f"<|im_start|>user: Given the following text:\n"
    +    "{}"
    +) 
    +
    +QUESTIONS_WRAPPER = (
    +    " answer the given questions as accurately as you can, do not write more answers the questions:\n"
    +    "{}<|im_end|>\n"
    +    "<|im_start|>assistant:\n"
    +)
    +
    +
    +
    +
    +

    The config is for the second questioning method, we cal “poll”, and in which we need to choose how many voting models we want participating,
    +and in what way we want do decide the result, we currentlly support average and most_common as show here.

    +

    *An explenation about both questioning methods can be found in the begginig of this notebook

    +
    +
    +
    questions_config = 
    +    {
    +        "type": "poll",
    +        "poll_count": 3, # How many 'voters'
    +        "poll_strategy": "most_common"
    +    }
    +
    +
    +
    +
    +
    +
    +
    qa_model = "TheBloke/Mistral-7B-OpenOrca-GPTQ"
    +
    +
    +
    +
    +

    Finnaly, we run the function with all the parameters we prepared.

    +
    +
    +
    # Question answering:
    +demo3_run = func.run(
    +    function="question-answering",
    +    local=True,
    +    handler="answer_questions",
    +    inputs={"data_path": os.path.abspath("./calls")},
    +    params={
    +        "model_name": qa_model,
    +        "device_map": "auto",
    +        "text_wrapper":TEXT_WRAPPER,
    +        "questions": QUESTIONS,
    +        "questions_wrapper": QUESTIONS_WRAPPER,
    +        "questions_columns": qa_questions_columns,
    +        "questions_config": questions_config, # This time we add 'questions_config'
    +    },
    +    returns=[
    +        "question_answering_df: dataset",
    +        "question_answering_errors: result",
    +    ],
    +)
    +
    +
    +
    +
    +
    +
    +

    (3.) Review results#

    +
    +
    +
    demo3_run.outputs
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/static/function.html b/functions/master/question_answering/0.5.0/static/function.html new file mode 100644 index 00000000..658c382a --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/function.html @@ -0,0 +1,232 @@ + + + + + + + + + + + Source + + + + +
    +        
    +metadata:
    +  name: question-answering
    +  tag: ''
    +  categories:
    +  - genai
    +verbose: false
    +kind: job
    +spec:
    +  command: ''
    +  default_handler: answer_questions
    +  build:
    +    origin_filename: ''
    +    base_image: mlrun/mlrun
    +    requirements:
    +    - transformers
    +    - torch
    +    - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K
    +  entry_points:
    +    open_mpi_handler:
    +      name: open_mpi_handler
    +      has_varargs: false
    +      doc: ''
    +      lineno: 58
    +      parameters:
    +      - name: worker_inputs
    +        type: List[str]
    +      - name: root_worker_inputs
    +        type: Dict[str, Any]
    +        default: null
    +      has_kwargs: false
    +    decorator:
    +      name: decorator
    +      has_varargs: false
    +      doc: ''
    +      lineno: 66
    +      parameters:
    +      - name: handler
    +      has_kwargs: false
    +    wrapper:
    +      name: wrapper
    +      has_varargs: false
    +      doc: ''
    +      lineno: 71
    +      has_kwargs: true
    +    answer_questions:
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[pd.DataFrame, dict]
    +      name: answer_questions
    +      has_varargs: false
    +      doc: 'Answer questions with a context to the given text files contents by a
    +        pretrained LLM model. Each text file will have
    +
    +        the following prompt built:
    +
    +
    +        start of `text_wrapper`
    +
    +        
    +
    +        end of `text_wrapper`
    +
    +
    +        start of `questions_wrapper`
    +
    +        1. 
    +
    +        2. 
    +
    +        ...
    +
    +        n. 
    +
    +        end of `questions_wrapper`'
    +      lineno: 130
    +      parameters:
    +      - name: data_path
    +        type: Union[str, List[str]]
    +        doc: A path to a directory of text files or a path to a text file to ask questions
    +          about.
    +      - name: model_name
    +        type: str
    +        doc: The pre-trained model name from the huggingface hub to use for asking
    +          questions.
    +      - name: questions
    +        type: Union[List[str], List[List[str]]]
    +        doc: The questions to ask. A list of lists of questions to ask per text file,
    +          and devided by question groups, the groups can be dtermained by size (in
    +          order to avoid large inputs to the llm) or by questioning method (regular
    +          or poll like questioning).
    +      - name: device_map
    +        type: Union[str, dict]
    +        doc: A map to use for loading the model on multiple devices.
    +        default: null
    +      - name: model_kwargs
    +        type: dict
    +        doc: Keyword arguments to pass for loading the model using HuggingFace's `transformers.AutoModelForCausalLM.from_pretrained`
    +          function.
    +        default: null
    +      - name: auto_gptq_exllama_max_input_length
    +        type: int
    +        doc: For AutoGPTQ models to set and extend the model's input buffer size.
    +        default: null
    +      - name: tokenizer_name
    +        type: str
    +        doc: The tokenizer name from the huggingface hub to use. If not given, the
    +          model name will be used.
    +        default: null
    +      - name: tokenizer_kwargs
    +        type: dict
    +        doc: Keyword arguments to pass for loading the tokenizer using HuggingFace's
    +          `transformers.AutoTokenizer.from_pretrained` function.
    +        default: null
    +      - name: text_wrapper
    +        type: Union[str, List[str]]
    +        doc: A wrapper for the file's text. Will be added at the start of the prompt.
    +          Must have a placeholder ('{}') for the text of the file.
    +        default: ''
    +      - name: questions_wrapper
    +        type: Union[str, List[str]]
    +        doc: A wrapper for the questions received. Will be added after the text wrapper
    +          in the prompt template. Must have a placeholder ('{}') for the questions.
    +        default: ''
    +      - name: generation_config
    +        type: Union[Dict, List[Dict]]
    +        doc: HuggingFace's `GenerationConfig` keyword arguments to pass to the `generate`
    +          method.
    +        default: null
    +      - name: questions_config
    +        type: Union[Dict, List[Dict]]
    +        doc: A dictionary or list of dictionaries containing specific ways to answer
    +          questions (using a poll for example), each dictionary in the list is for
    +          corresponding question group and determines the question asking method for
    +          said group.
    +        default: null
    +      - name: batch_size
    +        type: int
    +        doc: Batch size for inference.
    +        default: 1
    +      - name: questions_columns
    +        type: List[str]
    +        doc: Columns to use for the dataframe returned.
    +        default: null
    +      - name: verbose
    +        type: bool
    +        doc: 'Whether to present logs of a progress bar and errors. Default: True.'
    +        default: false
    +      has_kwargs: false
    +    answer:
    +      outputs:
    +      - type: List[List[str]]
    +      name: answer
    +      has_varargs: false
    +      doc: Answer questions with a context to the given text files contents by a pretrained
    +        LLM model in given pipeline.
    +      lineno: 674
    +      parameters:
    +      - name: self
    +      - name: questions_amount
    +        type: int
    +      - name: batched_input
    +        type: List[str]
    +      - name: generation_pipeline
    +        type: Pipeline
    +      - name: generation_config
    +        type: GenerationConfig
    +      has_kwargs: false
    +    most_common:
    +      name: most_common
    +      has_varargs: false
    +      doc: Calculate the most common answer for a given list of answers.
    +      lineno: 637
    +      parameters:
    +      - name: answers
    +      has_kwargs: false
    +    average:
    +      name: average
    +      has_varargs: false
    +      doc: Calculate the average answer for a given list of answers.
    +      lineno: 646
    +      parameters:
    +      - name: answers
    +      has_kwargs: false
    +    do:
    +      name: do
    +      has_varargs: false
    +      doc: Perform the strategy.
    +      lineno: 662
    +      parameters:
    +      - name: self
    +      - name: answers
    +      has_kwargs: false
    +  image: ''
    +  description: GenAI approach of question answering on a given data
    +  disable_auto_mount: false
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/static/item.html b/functions/master/question_answering/0.5.0/static/item.html new file mode 100644 index 00000000..64e45da7 --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/item.html @@ -0,0 +1,62 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- genai
    +description: GenAI approach of question answering on a given data
    +doc: ''
    +example: question_answering.ipynb
    +generationDate: 2023-08-07:11-30
    +hidden: false
    +icon: ''
    +labels:
    +  author: yonish
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: question_answering
    +platformVersion: 3.5.0
    +spec:
    +  filename: question_answering.py
    +  handler: answer_questions
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +    - transformers
    +    - torch
    +    - tqdm
    +url: ''
    +version: 0.5.0
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/static/question_answering.html b/functions/master/question_answering/0.5.0/static/question_answering.html new file mode 100644 index 00000000..79c5db68 --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/question_answering.html @@ -0,0 +1,947 @@ + + + + + + + +question_answering.question_answering + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for question_answering.question_answering

    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import enum
    +import logging
    +import operator
    +import pathlib
    +from collections import Counter
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import transformers
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    global _LOGGER
    +
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        _LOGGER = context.logger
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +
    +[docs] +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_text_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + dataframe = pd.concat(objs=[df for df, _ in output], axis=0) + errors_dictionary = reduce(operator.ior, [err for _, err in output], {}) + return dataframe, errors_dictionary + return None + + return wrapper + + return decorator
    + + + +
    +[docs] +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def answer_questions( + data_path: Union[str, List[str]], + model_name: str, + questions: Union[List[str], List[List[str]]], + device_map: Union[str, dict] = None, + model_kwargs: dict = None, + auto_gptq_exllama_max_input_length: int = None, + tokenizer_name: str = None, + tokenizer_kwargs: dict = None, + text_wrapper: Union[str, List[str]] = "", + questions_wrapper: Union[str, List[str]] = "", + generation_config: Union[Dict, List[Dict]] = None, + questions_config: Union[Dict, List[Dict]] = None, + batch_size: int = 1, + questions_columns: List[str] = None, + verbose: bool = False, +) -> Tuple[pd.DataFrame, dict]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model. Each text file will have + the following prompt built: + + start of `text_wrapper` + <text file content> + end of `text_wrapper` + + start of `questions_wrapper` + 1. <questions[0]> + 2. <questions[1]> + ... + n. <questions[n-1]> + end of `questions_wrapper` + + :param data_path: A path to a directory of text files or a path to a text file to ask + questions about. + :param model_name: The pre-trained model name from the huggingface hub to use for asking + questions. + :param questions: The questions to ask. + A list of lists of questions to ask per text file, and devided + by question groups, the groups can be dtermained by size (in order to + avoid large inputs to the llm) or by questioning method + (regular or poll like questioning). + :param device_map: A map to use for loading the model on multiple devices. + :param model_kwargs: Keyword arguments to pass for loading the model using HuggingFace's + `transformers.AutoModelForCausalLM.from_pretrained` function. + :param auto_gptq_exllama_max_input_length: For AutoGPTQ models to set and extend the model's input buffer size. + :param tokenizer_name: The tokenizer name from the huggingface hub to use. If not given, the + model name will be used. + :param tokenizer_kwargs: Keyword arguments to pass for loading the tokenizer using HuggingFace's + `transformers.AutoTokenizer.from_pretrained` function. + :param text_wrapper: A wrapper for the file's text. Will be added at the start of the prompt. + Must have a placeholder ('{}') for the text of the file. + :param questions_wrapper: A wrapper for the questions received. Will be added after the text + wrapper in the prompt template. Must have a placeholder ('{}') for the + questions. + :param generation_config: HuggingFace's `GenerationConfig` keyword arguments to pass to the + `generate` method. + :param questions_config: A dictionary or list of dictionaries containing specific ways to answer + questions (using a poll for example), each dictionary in the list is for + corresponding question group and determines the question asking method + for said group. + :param batch_size: Batch size for inference. + :param questions_columns: Columns to use for the dataframe returned. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + + :returns: A tuple of: + + * A dataframe dataset of the questions answers. + * A dictionary of errored files that were not inferred or were not answered properly. + """ + global _LOGGER + + # Set configs to empty dict if not given: + if generation_config is None: + generation_config = {} + if questions_config is None: + questions_config = {} + + # Get the input text files to question: + if verbose: + _LOGGER.info("Collecting text files.") + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + text_files = _get_text_files(data_path=data_path) + else: + text_files = data_path + if verbose: + _LOGGER.info(f"Collected {len(text_files)} text files.") + + # Get the prompt template: + if verbose: + _LOGGER.info("Creating prompt template.") + + # Organize questions as a list of list, and count number of sub-lists for future use + number_of_question_groups = 1 if isinstance(questions[0], str) else len(questions) + questions = _to_group_list( + argument_value=questions, + argument_name="questions", + length=number_of_question_groups, + ) + + # Organize prompt parts at proper length + text_wrapper = _to_group_list( + argument_value=text_wrapper, + argument_name="text_wrapper", + length=number_of_question_groups, + ) + questions_wrapper = _to_group_list( + argument_value=questions_wrapper, + argument_name="questions_wrapper", + length=number_of_question_groups, + ) + + # Create a list of prompt according to given parts and questions + prompt_template = [] + questions = questions if isinstance(questions[0], list) else [questions] + + # Build all prompts + for i in range(number_of_question_groups): + prompt_template.append( + _get_prompt_template( + text_wrapper=text_wrapper[i], + questions_wrapper=questions_wrapper[i], + questions=questions[i], + ) + ) + if verbose: + _LOGGER.info(f"Prompt template created:\n\n{prompt_template}\n") + + # Get the total amount of questions: + questions_amount = sum([len(sublist) for sublist in questions]) + + # Get the questions columns: + questions_columns = questions_columns or [ + f"q{i}" for i in range(1, questions_amount + 1) + ] + + # Check if we have the correct amount of questions columns: + if len(questions_columns) != questions_amount: + raise ValueError( + f"The provided questions columns length ({len(questions_columns)}) " + f"does not match the questions amount ({questions_amount})" + ) + + # Load the generation config: + if verbose: + _LOGGER.info("Loading generation configuration.") + generation_config = [ + transformers.GenerationConfig(**(cfg or {})) + for cfg in _to_group_list( + argument_value=generation_config, + argument_name="generation_config", + length=number_of_question_groups, + ) + ] + if verbose: + _LOGGER.info(f"Generation configuration loaded: {generation_config}") + + # Load the model and tokenizer into a pipeline object: + if verbose: + _LOGGER.info(f"Loading model '{model_name}'.") + generation_pipeline = _get_generation_pipeline( + model_name=model_name, + device_map=device_map, + tokenizer_name=tokenizer_name or model_name, + model_kwargs=model_kwargs or {}, + tokenizer_kwargs=tokenizer_kwargs or {}, + auto_gptq_exllama_max_input_length=auto_gptq_exllama_max_input_length, + batch_size=batch_size, + ) + if verbose: + _LOGGER.info("Model loaded.") + + # Prepare the successes dataframe and errors dictionary to be returned: + successes = [] + errors = {} + + # Split the files into batches: + file_batches = [ + text_files[i : i + batch_size] + if i + batch_size < len(text_files) + else text_files[i:] + for i in range(0, len(text_files), batch_size) + ] + questions_config = _to_group_list( + argument_value=questions_config, + argument_name="questions_config", + length=number_of_question_groups, + ) + + # Create a list of question handlers according to given configs + handlers = [] + for cfg in questions_config: + question_type = cfg.pop("type", "default") + handlers.append(QUESTION_MAPPING.get(question_type)(**cfg)) + + # Go over the batches of text files and question them: + for file_batch in tqdm( + file_batches, + desc="Generating answers", + unit=f"file (batch of {batch_size})", + disable=not verbose, + ): + try: + total_answers = [[] for _ in range(batch_size)] + + # Go over all question group per batch of documents + for question_group in range(number_of_question_groups): + current_questions_amount = len(questions[question_group]) + + # Read batch (read the text from the text files): + batched_input = _read_file_batch( + file_batch=file_batch, + prompt_template=prompt_template[question_group], + ) + + # Answer the questions with each question handler: + batched_answers = handlers[question_group].answer( + questions_amount=current_questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config[question_group], + ) + + # Put the answers in the correct place in the total answers list according to the place in the batch: + for i in range(batch_size): + total_answers[i].extend(batched_answers[i]) + + # Collect the answers and attach the file name: + successes.extend( + [ + [file.name, *answers] + for file, answers in zip(file_batch, total_answers) + ] + ) + except Exception as exception: + # Note the exception as error in the dictionary: + batch_file_names = ", ".join([file.name for file in file_batch]) + if verbose: + _LOGGER.warning( + f"Error in batch '{batch_file_names}': {str(exception)}" + ) + errors[batch_file_names] = str(exception) + continue + + # Construct the answers dataframe: + columns = [ + "text_file", + *questions_columns, + ] + + # Create a data frame of answers by files + successes = pd.DataFrame( + successes, + columns=columns, + ) + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(text_files)})\n" + f"Answers summary:\n" + f"{successes.head()}" + ) + return successes, errors
    + + + +def _get_text_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + + # Get all files inside the directory: + text_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + text_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return text_files + + +def _get_prompt_template( + text_wrapper: str, + questions_wrapper: str, + questions: List[str], +) -> str: + + # Validate and build the text wrapper: + text_wrapper = text_wrapper or ( + "Given the following text:\n" "-----\n" "{}\n" "-----" + ) + if text_wrapper.count("{}") != 1: + raise ValueError( + "The `text_wrapper` must include one placeholder '{}' for the text of the file to be asked about." + ) + + # Validate and build the question wrapper: + questions_wrapper = questions_wrapper or "Answer the questions:\n" "{}" + if questions_wrapper.count("{}") != 1: + raise ValueError( + "The `questions_wrapper` must include one placeholder '{}' for the list of questions." + ) + + # Validate and parse the questions: + if len(questions) == 0: + raise ValueError("Please include at least one question.") + questions = "\n".join( + [f"{i}. {question}" for i, question in enumerate(questions, 1)] + ) + + # Construct the template: + return f"{text_wrapper}\n{questions_wrapper.format(questions)}\n" + + +def _get_generation_pipeline( + model_name: str, + device_map: Union[str, dict], + tokenizer_name: str, + model_kwargs: dict, + tokenizer_kwargs: dict, + auto_gptq_exllama_max_input_length: int = None, + batch_size: int = 1, +): + # Load the model: + model = transformers.AutoModelForCausalLM.from_pretrained( + model_name, device_map=device_map, **model_kwargs + ) + + # Set exllama max input length if provided: + # This changes the model's context size. + if auto_gptq_exllama_max_input_length: + from auto_gptq import exllama_set_max_input_length + + model = exllama_set_max_input_length( + model=model, max_input_length=auto_gptq_exllama_max_input_length + ) + + # Load the tokenizer: + tokenizer = transformers.AutoTokenizer.from_pretrained( + tokenizer_name, **tokenizer_kwargs + ) + + # Initialize a generation pipline and return: + pipe = transformers.pipeline( + task="text-generation", + model=model, + tokenizer=tokenizer, + batch_size=batch_size, + ) + pipe.tokenizer.pad_token_id = model.config.eos_token_id + return pipe + + +def _read_file_batch( + file_batch: List[pathlib.Path], + prompt_template: str, +) -> List[str]: + batch = [] + + # Go over all files and read in usable format + for file in file_batch: + with open(file, "r", encoding="utf-8") as fp: + batch.append(prompt_template.format(fp.read())) + return batch + + +def _to_group_list(argument_value: list, argument_name: str, length: int): + + # Check if is list, turn to list if not + argument_value = ( + argument_value if isinstance(argument_value, list) else [argument_value] + ) + list_len = len(argument_value) + + # If not a list, or is a list of len 1 we duplicate for correct length + # If list in wrong length throw an error + if list_len != length: + if list_len == 1: + return argument_value * length + raise ValueError( + f"The argument value of '{argument_name}' is not equal to the length of the given questions - {length}" + ) + return argument_value + + +
    +[docs] +class QuestionHandler: + """ + A class for handling questions answering for a given question type. + This class is used as a base class for all question types, and for default question type (regular question + answering without any special handling). + """ + +
    +[docs] + class ConfigKeys: + pass
    + + + def __init__(self): + pass + + @staticmethod + def _get_answers(generated_text: str, questions_amount: int) -> List[str]: + + # Clear answer start (part before numbers): + # TODO find better way to verify, for list of questions this is redundant for example + if "1." not in generated_text: + raise ValueError( + f"Answer 1. is missing from the generated text: '{generated_text}'" + ) + text = generated_text.split("1.", 1)[1] + + # Start extracting the answers: + answers = [] + for i in range(1, questions_amount + 1): + # If it's the last answer to look for, take the rest of the text: + if i == questions_amount: + answer_i = text + # Verify there is a question number in the text: + elif f"{i + 1}." not in text: + raise ValueError( + f"Answer {i + 1}. is missing from the generated text: '{generated_text}'" + ) + # Take i's answer: + else: + answer_i, text = text.split(f"{i + 1}.", 1) + # Collect the answer removing redundant spaces: + answers.append(answer_i.strip()) + + return answers + + def _infer_questions( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + + # Infer through the llm: + batched_output = generation_pipeline( + batched_input, + generation_config=generation_config, + eos_token_id=generation_pipeline.tokenizer.eos_token_id, + return_full_text=False, + num_return_sequences=1, + ) + + # Process the outputs to get the answers: + batched_answers = [] + for output in batched_output: + # Get the generated answers: + answers = self._get_answers( + generated_text=output[0]["generated_text"], + questions_amount=questions_amount, + ) + # Collect the processed answers: + batched_answers.append(answers) + return batched_answers + +
    +[docs] + def answer( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline. + """ + return self._infer_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + )
    +
    + + + +
    +[docs] +class PollQuestionHandler(QuestionHandler): + """ + Static class to hold all the possible poll question configurations options keys + """ + +
    +[docs] + class ConfigKeys: + """ + A class for handling questions answering for poll type questions. + These type of question are answered by asking the same question multiple times + and choosing the most common answer or the average answer. + """ + + #: The number of times to ask the same question. + POLL_COUNT = "poll_count" + + #: The strategy to use for choosing the answer from the poll. + POLL_STRATEGY = "poll_strategy"
    + + +
    +[docs] + class Strategy(enum.Enum): + #: The most common answer strategy. + MOST_COMMON = "most_common" + + #: The average answer strategy. + AVERAGE = "average" + +
    +[docs] + @staticmethod + def most_common(answers): + """ + Calculate the most common answer for a given list of answers. + """ + count = Counter(answers) + most_common = count.most_common(1) + return most_common[0][0]
    + + +
    +[docs] + @staticmethod + def average(answers): + """ + Calculate the average answer for a given list of answers. + """ + if isinstance(answers[0], str): + raise ValueError( + "Cannot perform poll with average answer strategy of non numeric values," + " please change the question to give numeric data, or choose 'most_common' as strategy." + ) + else: + numeric_values = answers + avg = sum(numeric_values) / len(numeric_values) + + # Round to the closest integer and return corresponding value + return round(avg)
    + + +
    +[docs] + def do(self, answers): + """ + Perform the strategy. + """ + return getattr(self, self.value)(answers)
    +
    + + + def __init__( + self, poll_count: int = 5, poll_strategy: str = "most_common"): + super().__init__() + self.poll_count = poll_count + self.poll_strategy = self.Strategy(poll_strategy) + +
    +[docs] + def answer( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + """ + Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline. + """ + return self._answer_poll_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + )
    + + + def _answer_poll_questions( + self, + questions_amount: int, + batched_input: List[str], + generation_pipeline: transformers.Pipeline, + generation_config: transformers.GenerationConfig, + ) -> List[List[str]]: + votes = [] + + # Run the poll for each question + for _ in range(self.poll_count): + batched_answers = self._infer_questions( + questions_amount=questions_amount, + batched_input=batched_input, + generation_pipeline=generation_pipeline, + generation_config=generation_config, + ) + votes.append(batched_answers) + answers = [] + + # Collect the answers according to the poll strategy + # Average strategy works for numeric values only + for batch in range(len(votes[0])): + batched_answers = [] + for question in range(questions_amount): + # Create a list of all answers to relevant question + answer = [ + votes[voter][batch][question] for voter in range(self.poll_count) + ] + answer = self.poll_strategy.do(answer) + batched_answers.append(answer) + answers.append(batched_answers) + return answers
    + + + +# Holds names of QuestionHandles +
    +[docs] +class QuestionTypes: + DEFAULT = "default" + POLL = "poll"
    + + + +# Maps question types to their handlers +QUESTION_MAPPING = { + QuestionTypes.DEFAULT: QuestionHandler, + QuestionTypes.POLL: PollQuestionHandler, +} +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/question_answering/0.5.0/static/source.html b/functions/master/question_answering/0.5.0/static/source.html new file mode 100644 index 00000000..40c2e482 --- /dev/null +++ b/functions/master/question_answering/0.5.0/static/source.html @@ -0,0 +1,771 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import enum
    +import logging
    +import operator
    +import pathlib
    +from collections import Counter
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import transformers
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    global _LOGGER
    +
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        _LOGGER = context.logger
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +def open_mpi_handler(
    +    worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None
    +):
    +    global _LOGGER
    +
    +    # Check for MLRun and OpenMPI availability:
    +    context, comm = _check_mlrun_and_open_mpi()
    +
    +    def decorator(handler):
    +        if comm is None or comm.Get_size() == 1:
    +            return handler
    +
    +        @wraps(handler)
    +        def wrapper(**kwargs):
    +            # Get the open mpi environment properties:
    +            size = comm.Get_size()
    +            rank = comm.Get_rank()
    +
    +            # Give the correct chunk of the workers inputs:
    +            for worker_input in worker_inputs:
    +                input_argument = kwargs[worker_input]
    +                if input_argument is None:
    +                    continue
    +                if isinstance(input_argument, str):
    +                    input_argument = _get_text_files(
    +                        data_path=pathlib.Path(input_argument).absolute()
    +                    )
    +                if len(input_argument) < size:
    +                    raise ValueError(
    +                        f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. "
    +                        f"Please reduce the amount of workers for this input."
    +                    )
    +                even_chunk_size = len(input_argument) // size
    +                chunk_start = rank * even_chunk_size
    +                chunk_end = (
    +                    (rank + 1) * even_chunk_size
    +                    if rank + 1 < size
    +                    else len(input_argument)
    +                )
    +                context.logger.info(
    +                    f"Rank #{rank}: Processing input chunk of '{worker_input}' "
    +                    f"from index {chunk_start} to {chunk_end}."
    +                )
    +                if isinstance(input_argument, list):
    +                    input_argument = input_argument[chunk_start:chunk_end]
    +                elif isinstance(input_argument, pd.DataFrame):
    +                    input_argument = input_argument.iloc[chunk_start:chunk_end:, :]
    +                kwargs[worker_input] = input_argument
    +
    +            # Set the root worker only arguments:
    +            if rank == 0 and root_worker_inputs:
    +                kwargs.update(root_worker_inputs)
    +
    +            # Run the worker:
    +            output = handler(**kwargs)
    +
    +            # Send the output to the root rank (rank #0):
    +            output = comm.gather(output, root=0)
    +            if rank == 0:
    +                # Join the outputs:
    +                context.logger.info("Collecting data from workers to root worker.")
    +                dataframe = pd.concat(objs=[df for df, _ in output], axis=0)
    +                errors_dictionary = reduce(operator.ior, [err for _, err in output], {})
    +                return dataframe, errors_dictionary
    +            return None
    +
    +        return wrapper
    +
    +    return decorator
    +
    +
    +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True})
    +def answer_questions(
    +    data_path: Union[str, List[str]],
    +    model_name: str,
    +    questions: Union[List[str], List[List[str]]],
    +    device_map: Union[str, dict] = None,
    +    model_kwargs: dict = None,
    +    auto_gptq_exllama_max_input_length: int = None,
    +    tokenizer_name: str = None,
    +    tokenizer_kwargs: dict = None,
    +    text_wrapper: Union[str, List[str]] = "",
    +    questions_wrapper: Union[str, List[str]] = "",
    +    generation_config: Union[Dict, List[Dict]] = None,
    +    questions_config: Union[Dict, List[Dict]] = None,
    +    batch_size: int = 1,
    +    questions_columns: List[str] = None,
    +    verbose: bool = False,
    +) -> Tuple[pd.DataFrame, dict]:
    +    """
    +    Answer questions with a context to the given text files contents by a pretrained LLM model. Each text file will have
    +    the following prompt built:
    +
    +    start of `text_wrapper`
    +    
    +    end of `text_wrapper`
    +
    +    start of `questions_wrapper`
    +    1. 
    +    2. 
    +    ...
    +    n. 
    +    end of `questions_wrapper`
    +
    +    :param data_path:                          A path to a directory of text files or a path to a text file to ask
    +                                               questions about.
    +    :param model_name:                         The pre-trained model name from the huggingface hub to use for asking
    +                                               questions.
    +    :param questions:                          The questions to ask.
    +                                               A list of lists of questions to ask per text file, and devided
    +                                               by question groups, the groups can be dtermained by size (in order to
    +                                               avoid large inputs to the llm) or by questioning method
    +                                               (regular or poll like questioning).
    +    :param device_map:                         A map to use for loading the model on multiple devices.
    +    :param model_kwargs:                       Keyword arguments to pass for loading the model using HuggingFace's
    +                                               `transformers.AutoModelForCausalLM.from_pretrained` function.
    +    :param auto_gptq_exllama_max_input_length: For AutoGPTQ models to set and extend the model's input buffer size.
    +    :param tokenizer_name:                     The tokenizer name from the huggingface hub to use. If not given, the
    +                                               model name will be used.
    +    :param tokenizer_kwargs:                   Keyword arguments to pass for loading the tokenizer using HuggingFace's
    +                                               `transformers.AutoTokenizer.from_pretrained` function.
    +    :param text_wrapper:                       A wrapper for the file's text. Will be added at the start of the prompt.
    +                                               Must have a placeholder ('{}') for the text of the file.
    +    :param questions_wrapper:                  A wrapper for the questions received. Will be added after the text
    +                                               wrapper in the prompt template. Must have a placeholder ('{}') for the
    +                                               questions.
    +    :param generation_config:                  HuggingFace's `GenerationConfig` keyword arguments to pass to the
    +                                               `generate` method.
    +    :param questions_config:                   A dictionary or list of dictionaries containing specific ways to answer
    +                                               questions (using a poll for example), each dictionary in the list is for
    +                                               corresponding question group and determines the question asking method
    +                                               for said group.
    +    :param batch_size:                         Batch size for inference.
    +    :param questions_columns:                  Columns to use for the dataframe returned.
    +    :param verbose:                            Whether to present logs of a progress bar and errors. Default: True.
    +
    +
    +    :returns: A tuple of:
    +
    +              * A dataframe dataset of the questions answers.
    +              * A dictionary of errored files that were not inferred or were not answered properly.
    +    """
    +    global _LOGGER
    +
    +    # Set configs to empty dict if not given:
    +    if generation_config is None:
    +        generation_config = {}
    +    if questions_config is None:
    +        questions_config = {}
    +
    +    # Get the input text files to question:
    +    if verbose:
    +        _LOGGER.info("Collecting text files.")
    +    if isinstance(data_path, str):
    +        data_path = pathlib.Path(data_path).absolute()
    +        text_files = _get_text_files(data_path=data_path)
    +    else:
    +        text_files = data_path
    +    if verbose:
    +        _LOGGER.info(f"Collected {len(text_files)} text files.")
    +
    +    # Get the prompt template:
    +    if verbose:
    +        _LOGGER.info("Creating prompt template.")
    +
    +    # Organize questions as a list of list, and count number of sub-lists for future use
    +    number_of_question_groups = 1 if isinstance(questions[0], str) else len(questions)
    +    questions = _to_group_list(
    +        argument_value=questions,
    +        argument_name="questions",
    +        length=number_of_question_groups,
    +    )
    +
    +    # Organize prompt parts at proper length
    +    text_wrapper = _to_group_list(
    +        argument_value=text_wrapper,
    +        argument_name="text_wrapper",
    +        length=number_of_question_groups,
    +    )
    +    questions_wrapper = _to_group_list(
    +        argument_value=questions_wrapper,
    +        argument_name="questions_wrapper",
    +        length=number_of_question_groups,
    +    )
    +
    +    # Create a list of prompt according to given parts and questions
    +    prompt_template = []
    +    questions = questions if isinstance(questions[0], list) else [questions]
    +
    +    # Build all prompts
    +    for i in range(number_of_question_groups):
    +        prompt_template.append(
    +            _get_prompt_template(
    +                text_wrapper=text_wrapper[i],
    +                questions_wrapper=questions_wrapper[i],
    +                questions=questions[i],
    +            )
    +        )
    +    if verbose:
    +        _LOGGER.info(f"Prompt template created:\n\n{prompt_template}\n")
    +
    +    # Get the total amount of questions:
    +    questions_amount = sum([len(sublist) for sublist in questions])
    +
    +    # Get the questions columns:
    +    questions_columns = questions_columns or [
    +        f"q{i}" for i in range(1, questions_amount + 1)
    +    ]
    +
    +    # Check if we have the correct amount of questions columns:
    +    if len(questions_columns) != questions_amount:
    +        raise ValueError(
    +            f"The provided questions columns length ({len(questions_columns)}) "
    +            f"does not match the questions amount ({questions_amount})"
    +        )
    +
    +    # Load the generation config:
    +    if verbose:
    +        _LOGGER.info("Loading generation configuration.")
    +    generation_config = [
    +        transformers.GenerationConfig(**(cfg or {}))
    +        for cfg in _to_group_list(
    +            argument_value=generation_config,
    +            argument_name="generation_config",
    +            length=number_of_question_groups,
    +        )
    +    ]
    +    if verbose:
    +        _LOGGER.info(f"Generation configuration loaded: {generation_config}")
    +
    +    # Load the model and tokenizer into a pipeline object:
    +    if verbose:
    +        _LOGGER.info(f"Loading model '{model_name}'.")
    +    generation_pipeline = _get_generation_pipeline(
    +        model_name=model_name,
    +        device_map=device_map,
    +        tokenizer_name=tokenizer_name or model_name,
    +        model_kwargs=model_kwargs or {},
    +        tokenizer_kwargs=tokenizer_kwargs or {},
    +        auto_gptq_exllama_max_input_length=auto_gptq_exllama_max_input_length,
    +        batch_size=batch_size,
    +    )
    +    if verbose:
    +        _LOGGER.info("Model loaded.")
    +
    +    # Prepare the successes dataframe and errors dictionary to be returned:
    +    successes = []
    +    errors = {}
    +
    +    # Split the files into batches:
    +    file_batches = [
    +        text_files[i : i + batch_size]
    +        if i + batch_size < len(text_files)
    +        else text_files[i:]
    +        for i in range(0, len(text_files), batch_size)
    +    ]
    +    questions_config = _to_group_list(
    +        argument_value=questions_config,
    +        argument_name="questions_config",
    +        length=number_of_question_groups,
    +    )
    +
    +    # Create a list of question handlers according to given configs
    +    handlers = []
    +    for cfg in questions_config:
    +        question_type = cfg.pop("type", "default")
    +        handlers.append(QUESTION_MAPPING.get(question_type)(**cfg))
    +
    +    # Go over the batches of text files and question them:
    +    for file_batch in tqdm(
    +        file_batches,
    +        desc="Generating answers",
    +        unit=f"file (batch of {batch_size})",
    +        disable=not verbose,
    +    ):
    +        try:
    +            total_answers = [[] for _ in range(batch_size)]
    +
    +            # Go over all question group per batch of documents
    +            for question_group in range(number_of_question_groups):
    +                current_questions_amount = len(questions[question_group])
    +
    +                # Read batch (read the text from the text files):
    +                batched_input = _read_file_batch(
    +                    file_batch=file_batch,
    +                    prompt_template=prompt_template[question_group],
    +                )
    +
    +                # Answer the questions with each question handler:
    +                batched_answers = handlers[question_group].answer(
    +                    questions_amount=current_questions_amount,
    +                    batched_input=batched_input,
    +                    generation_pipeline=generation_pipeline,
    +                    generation_config=generation_config[question_group],
    +                )
    +
    +                # Put the answers in the correct place in the total answers list according to the place in the batch:
    +                for i in range(batch_size):
    +                    total_answers[i].extend(batched_answers[i])
    +
    +            # Collect the answers and attach the file name:
    +            successes.extend(
    +                [
    +                    [file.name, *answers]
    +                    for file, answers in zip(file_batch, total_answers)
    +                ]
    +            )
    +        except Exception as exception:
    +            # Note the exception as error in the dictionary:
    +            batch_file_names = ", ".join([file.name for file in file_batch])
    +            if verbose:
    +                _LOGGER.warning(
    +                    f"Error in batch '{batch_file_names}': {str(exception)}"
    +                )
    +            errors[batch_file_names] = str(exception)
    +            continue
    +
    +    # Construct the answers dataframe:
    +    columns = [
    +        "text_file",
    +        *questions_columns,
    +    ]
    +
    +    # Create a data frame of answers by files
    +    successes = pd.DataFrame(
    +        successes,
    +        columns=columns,
    +    )
    +
    +    # Print the head of the produced dataframe and return:
    +    if verbose:
    +        _LOGGER.info(
    +            f"Done ({successes.shape[0]}/{len(text_files)})\n"
    +            f"Answers summary:\n"
    +            f"{successes.head()}"
    +        )
    +    return successes, errors
    +
    +
    +def _get_text_files(
    +    data_path: pathlib.Path,
    +) -> List[pathlib.Path]:
    +
    +    # Check if the path is of a directory or a file:
    +    if data_path.is_dir():
    +
    +        # Get all files inside the directory:
    +        text_files = list(data_path.glob("*.*"))
    +    elif data_path.is_file():
    +        text_files = [data_path]
    +    else:
    +        raise ValueError(
    +            f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. "
    +            f"Given: {str(data_path)} "
    +        )
    +
    +    return text_files
    +
    +
    +def _get_prompt_template(
    +    text_wrapper: str,
    +    questions_wrapper: str,
    +    questions: List[str],
    +) -> str:
    +
    +    # Validate and build the text wrapper:
    +    text_wrapper = text_wrapper or (
    +        "Given the following text:\n" "-----\n" "{}\n" "-----"
    +    )
    +    if text_wrapper.count("{}") != 1:
    +        raise ValueError(
    +            "The `text_wrapper` must include one placeholder '{}' for the text of the file to be asked about."
    +        )
    +
    +    # Validate and build the question wrapper:
    +    questions_wrapper = questions_wrapper or "Answer the questions:\n" "{}"
    +    if questions_wrapper.count("{}") != 1:
    +        raise ValueError(
    +            "The `questions_wrapper` must include one placeholder '{}' for the list of questions."
    +        )
    +
    +    # Validate and parse the questions:
    +    if len(questions) == 0:
    +        raise ValueError("Please include at least one question.")
    +    questions = "\n".join(
    +        [f"{i}. {question}" for i, question in enumerate(questions, 1)]
    +    )
    +
    +    # Construct the template:
    +    return f"{text_wrapper}\n{questions_wrapper.format(questions)}\n"
    +
    +
    +def _get_generation_pipeline(
    +    model_name: str,
    +    device_map: Union[str, dict],
    +    tokenizer_name: str,
    +    model_kwargs: dict,
    +    tokenizer_kwargs: dict,
    +    auto_gptq_exllama_max_input_length: int = None,
    +    batch_size: int = 1,
    +):
    +    # Load the model:
    +    model = transformers.AutoModelForCausalLM.from_pretrained(
    +        model_name, device_map=device_map, **model_kwargs
    +    )
    +
    +    # Set exllama max input length if provided:
    +    # This changes the model's context size.
    +    if auto_gptq_exllama_max_input_length:
    +        from auto_gptq import exllama_set_max_input_length
    +
    +        model = exllama_set_max_input_length(
    +            model=model, max_input_length=auto_gptq_exllama_max_input_length
    +        )
    +
    +    # Load the tokenizer:
    +    tokenizer = transformers.AutoTokenizer.from_pretrained(
    +        tokenizer_name, **tokenizer_kwargs
    +    )
    +
    +    # Initialize a generation pipline and return:
    +    pipe = transformers.pipeline(
    +        task="text-generation",
    +        model=model,
    +        tokenizer=tokenizer,
    +        batch_size=batch_size,
    +    )
    +    pipe.tokenizer.pad_token_id = model.config.eos_token_id
    +    return pipe
    +
    +
    +def _read_file_batch(
    +    file_batch: List[pathlib.Path],
    +    prompt_template: str,
    +) -> List[str]:
    +    batch = []
    +
    +    # Go over all files and read in usable format
    +    for file in file_batch:
    +        with open(file, "r", encoding="utf-8") as fp:
    +            batch.append(prompt_template.format(fp.read()))
    +    return batch
    +
    +
    +def _to_group_list(argument_value: list, argument_name: str, length: int):
    +
    +    # Check if is list, turn to list if not
    +    argument_value = (
    +        argument_value if isinstance(argument_value, list) else [argument_value]
    +    )
    +    list_len = len(argument_value)
    +
    +    # If not a list, or is a list of len 1 we duplicate for correct length
    +    # If list in wrong length throw an error
    +    if list_len != length:
    +        if list_len == 1:
    +            return argument_value * length
    +        raise ValueError(
    +            f"The argument value of '{argument_name}' is not equal to the length of the given questions - {length}"
    +        )
    +    return argument_value
    +
    +
    +class QuestionHandler:
    +    """
    +    A class for handling questions answering for a given question type.
    +    This class is used as a base class for all question types, and for default question type (regular question
    +    answering without any special handling).
    +    """
    +
    +    class ConfigKeys:
    +        pass
    +
    +    def __init__(self):
    +        pass
    +
    +    @staticmethod
    +    def _get_answers(generated_text: str, questions_amount: int) -> List[str]:
    +
    +        # Clear answer start (part before numbers):
    +        # TODO find better way to verify, for list of questions this is redundant for example
    +        if "1." not in generated_text:
    +            raise ValueError(
    +                f"Answer 1. is missing from the generated text: '{generated_text}'"
    +            )
    +        text = generated_text.split("1.", 1)[1]
    +
    +        # Start extracting the answers:
    +        answers = []
    +        for i in range(1, questions_amount + 1):
    +            # If it's the last answer to look for, take the rest of the text:
    +            if i == questions_amount:
    +                answer_i = text
    +            # Verify there is a question number in the text:
    +            elif f"{i + 1}." not in text:
    +                raise ValueError(
    +                    f"Answer {i + 1}. is missing from the generated text: '{generated_text}'"
    +                )
    +            # Take i's answer:
    +            else:
    +                answer_i, text = text.split(f"{i + 1}.", 1)
    +            # Collect the answer removing redundant spaces:
    +            answers.append(answer_i.strip())
    +
    +        return answers
    +
    +    def _infer_questions(
    +        self,
    +        questions_amount: int,
    +        batched_input: List[str],
    +        generation_pipeline: transformers.Pipeline,
    +        generation_config: transformers.GenerationConfig,
    +    ) -> List[List[str]]:
    +
    +        # Infer through the llm:
    +        batched_output = generation_pipeline(
    +            batched_input,
    +            generation_config=generation_config,
    +            eos_token_id=generation_pipeline.tokenizer.eos_token_id,
    +            return_full_text=False,
    +            num_return_sequences=1,
    +        )
    +
    +        # Process the outputs to get the answers:
    +        batched_answers = []
    +        for output in batched_output:
    +            # Get the generated answers:
    +            answers = self._get_answers(
    +                generated_text=output[0]["generated_text"],
    +                questions_amount=questions_amount,
    +            )
    +            # Collect the processed answers:
    +            batched_answers.append(answers)
    +        return batched_answers
    +
    +    def answer(
    +        self,
    +        questions_amount: int,
    +        batched_input: List[str],
    +        generation_pipeline: transformers.Pipeline,
    +        generation_config: transformers.GenerationConfig,
    +    ) -> List[List[str]]:
    +        """
    +        Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline.
    +        """
    +        return self._infer_questions(
    +            questions_amount=questions_amount,
    +            batched_input=batched_input,
    +            generation_pipeline=generation_pipeline,
    +            generation_config=generation_config,
    +        )
    +
    +
    +class PollQuestionHandler(QuestionHandler):
    +    """
    +    Static class to hold all the possible poll question configurations options keys
    +    """
    +
    +    class ConfigKeys:
    +        """
    +        A class for handling questions answering for poll type questions.
    +        These type of question are answered by asking the same question multiple times
    +        and choosing the most common answer or the average answer.
    +        """
    +
    +        #: The number of times to ask the same question.
    +        POLL_COUNT = "poll_count"
    +
    +        #: The strategy to use for choosing the answer from the poll.
    +        POLL_STRATEGY = "poll_strategy"
    +
    +    class Strategy(enum.Enum):
    +        #: The most common answer strategy.
    +        MOST_COMMON = "most_common"
    +
    +        #: The average answer strategy.
    +        AVERAGE = "average"
    +
    +        @staticmethod
    +        def most_common(answers):
    +            """
    +            Calculate the most common answer for a given list of answers.
    +            """
    +            count = Counter(answers)
    +            most_common = count.most_common(1)
    +            return most_common[0][0]
    +
    +        @staticmethod
    +        def average(answers):
    +            """
    +            Calculate the average answer for a given list of answers.
    +            """
    +            if isinstance(answers[0], str):
    +                raise ValueError(
    +                    "Cannot perform poll with average answer strategy of non numeric values,"
    +                    " please change the question to give numeric data, or choose 'most_common' as strategy."
    +                )
    +            else:
    +                numeric_values = answers
    +            avg = sum(numeric_values) / len(numeric_values)
    +
    +            # Round to the closest integer and return corresponding value
    +            return round(avg)
    +
    +        def do(self, answers):
    +            """
    +            Perform the strategy.
    +            """
    +            return getattr(self, self.value)(answers)
    +
    +    def __init__(
    +        self, poll_count: int = 5, poll_strategy: str = "most_common"):
    +        super().__init__()
    +        self.poll_count = poll_count
    +        self.poll_strategy = self.Strategy(poll_strategy)
    +
    +    def answer(
    +        self,
    +        questions_amount: int,
    +        batched_input: List[str],
    +        generation_pipeline: transformers.Pipeline,
    +        generation_config: transformers.GenerationConfig,
    +    ) -> List[List[str]]:
    +        """
    +        Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline.
    +        """
    +        return self._answer_poll_questions(
    +            questions_amount=questions_amount,
    +            batched_input=batched_input,
    +            generation_pipeline=generation_pipeline,
    +            generation_config=generation_config,
    +        )
    +
    +    def _answer_poll_questions(
    +        self,
    +        questions_amount: int,
    +        batched_input: List[str],
    +        generation_pipeline: transformers.Pipeline,
    +        generation_config: transformers.GenerationConfig,
    +    ) -> List[List[str]]:
    +        votes = []
    +
    +        # Run the poll for each question
    +        for _ in range(self.poll_count):
    +            batched_answers = self._infer_questions(
    +                questions_amount=questions_amount,
    +                batched_input=batched_input,
    +                generation_pipeline=generation_pipeline,
    +                generation_config=generation_config,
    +            )
    +            votes.append(batched_answers)
    +        answers = []
    +
    +        # Collect the answers according to the poll strategy
    +        # Average strategy works for numeric values only
    +        for batch in range(len(votes[0])):
    +            batched_answers = []
    +            for question in range(questions_amount):
    +                # Create a list of all answers to relevant question
    +                answer = [
    +                    votes[voter][batch][question] for voter in range(self.poll_count)
    +                ]
    +                answer = self.poll_strategy.do(answer)
    +                batched_answers.append(answer)
    +            answers.append(batched_answers)
    +        return answers
    +
    +
    +# Holds names of QuestionHandles
    +class QuestionTypes:
    +    DEFAULT = "default"
    +    POLL = "poll"
    +
    +
    +# Maps question types to their handlers
    +QUESTION_MAPPING = {
    +    QuestionTypes.DEFAULT: QuestionHandler,
    +    QuestionTypes.POLL: PollQuestionHandler,
    +}
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/question_answering/latest/src/function.yaml b/functions/master/question_answering/latest/src/function.yaml index 7491b17e..21f741aa 100644 --- a/functions/master/question_answering/latest/src/function.yaml +++ b/functions/master/question_answering/latest/src/function.yaml @@ -1,62 +1,55 @@ -kind: job metadata: name: question-answering tag: '' - hash: aed62db95f17576c69b457767e3595c2de1d5465 - project: '' - labels: - author: yonish categories: - genai - - huggingface - - machine-learning +verbose: false +kind: job spec: command: '' - args: [] - image: '' + default_handler: answer_questions build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K - base_image: mlrun/mlrun - commands: [] - code_origin: '' origin_filename: '' + base_image: mlrun/mlrun requirements: - transformers - torch - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K entry_points: open_mpi_handler: name: open_mpi_handler + has_varargs: false doc: '' + lineno: 58 parameters: - name: worker_inputs type: List[str] - name: root_worker_inputs type: Dict[str, Any] default: null - outputs: [] - lineno: 58 - has_varargs: false has_kwargs: false decorator: name: decorator + has_varargs: false doc: '' + lineno: 66 parameters: - name: handler - outputs: [] - lineno: 66 - has_varargs: false has_kwargs: false wrapper: name: wrapper + has_varargs: false doc: '' - parameters: [] - outputs: [] lineno: 71 - has_varargs: false has_kwargs: true answer_questions: + outputs: + - doc: 'A tuple of:' + type: Tuple[pd.DataFrame, dict] name: answer_questions + has_varargs: false doc: 'Answer questions with a context to the given text files contents by a pretrained LLM model. Each text file will have @@ -81,6 +74,7 @@ spec: n. end of `questions_wrapper`' + lineno: 130 parameters: - name: data_path type: Union[str, List[str]] @@ -153,16 +147,15 @@ spec: type: bool doc: 'Whether to present logs of a progress bar and errors. Default: True.' default: false - outputs: - - doc: 'A tuple of:' - type: Tuple[pd.DataFrame, dict] - lineno: 130 - has_varargs: false has_kwargs: false answer: + outputs: + - type: List[List[str]] name: answer + has_varargs: false doc: Answer questions with a context to the given text files contents by a pretrained LLM model in given pipeline. + lineno: 674 parameters: - name: self - name: questions_amount @@ -173,47 +166,32 @@ spec: type: Pipeline - name: generation_config type: GenerationConfig - outputs: - - type: List[List[str]] - lineno: 674 - has_varargs: false has_kwargs: false most_common: name: most_common + has_varargs: false doc: Calculate the most common answer for a given list of answers. + lineno: 637 parameters: - name: answers - outputs: [] - lineno: 637 - has_varargs: false has_kwargs: false average: name: average + has_varargs: false doc: Calculate the average answer for a given list of answers. + lineno: 646 parameters: - name: answers - outputs: [] - lineno: 646 - has_varargs: false has_kwargs: false do: name: do + has_varargs: false doc: Perform the strategy. + lineno: 662 parameters: - name: self - name: answers - outputs: [] - lineno: 662 - has_varargs: false has_kwargs: false + image: '' description: GenAI approach of question answering on a given data - default_handler: answer_questions disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false diff --git a/functions/master/question_answering/latest/src/item.yaml b/functions/master/question_answering/latest/src/item.yaml index 56fc5a5e..741bab80 100755 --- a/functions/master/question_answering/latest/src/item.yaml +++ b/functions/master/question_answering/latest/src/item.yaml @@ -1,8 +1,6 @@ apiVersion: v1 categories: - genai -- huggingface -- machine-learning description: GenAI approach of question answering on a given data doc: '' example: question_answering.ipynb @@ -13,7 +11,7 @@ labels: author: yonish maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: question_answering platformVersion: 3.5.0 spec: @@ -26,4 +24,4 @@ spec: - torch - tqdm url: '' -version: 0.4.0 +version: 0.5.0 diff --git a/functions/master/question_answering/latest/static/documentation.html b/functions/master/question_answering/latest/static/documentation.html index 868c1682..602f654b 100644 --- a/functions/master/question_answering/latest/static/documentation.html +++ b/functions/master/question_answering/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/question_answering/latest/static/example.html b/functions/master/question_answering/latest/static/example.html index 5f8422d9..e54ca5e5 100644 --- a/functions/master/question_answering/latest/static/example.html +++ b/functions/master/question_answering/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/question_answering/latest/static/function.html b/functions/master/question_answering/latest/static/function.html index 53c64f1c..658c382a 100644 --- a/functions/master/question_answering/latest/static/function.html +++ b/functions/master/question_answering/latest/static/function.html @@ -28,65 +28,58 @@
             
    -kind: job
     metadata:
       name: question-answering
       tag: ''
    -  hash: aed62db95f17576c69b457767e3595c2de1d5465
    -  project: ''
    -  labels:
    -    author: yonish
       categories:
       - genai
    -  - huggingface
    -  - machine-learning
    +verbose: false
    +kind: job
     spec:
       command: ''
    -  args: []
    -  image: ''
    +  default_handler: answer_questions
       build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K
    -    base_image: mlrun/mlrun
    -    commands: []
    -    code_origin: ''
         origin_filename: ''
    +    base_image: mlrun/mlrun
         requirements:
         - transformers
         - torch
         - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgZW51bQppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IHBhdGhsaWIKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcgpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIHR5cGluZyBpbXBvcnQgQW55LCBEaWN0LCBMaXN0LCBUdXBsZSwgVW5pb24KCmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IHRyYW5zZm9ybWVycwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgpfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkgLT4gVHVwbGVbIm1scnVuLk1MQ2xpZW50Q3R4IiwgIm1waTRweS5NUEkuSW50cmFjb21tIl06CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1vZHVsZV9ub3RfZm91bmQ6CiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICByYWlzZSBtb2R1bGVfbm90X2ZvdW5kCiAgICByZXR1cm4gTm9uZSwgTm9uZQoKCmRlZiBvcGVuX21waV9oYW5kbGVyKAogICAgd29ya2VyX2lucHV0czogTGlzdFtzdHJdLCByb290X3dvcmtlcl9pbnB1dHM6IERpY3Rbc3RyLCBBbnldID0gTm9uZQopOgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIENoZWNrIGZvciBNTFJ1biBhbmQgT3Blbk1QSSBhdmFpbGFiaWxpdHk6CiAgICBjb250ZXh0LCBjb21tID0gX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfdGV4dF9maWxlcygKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9wYXRoPXBhdGhsaWIuUGF0aChpbnB1dF9hcmd1bWVudCkuYWJzb2x1dGUoKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGxlbihpbnB1dF9hcmd1bWVudCkgPCBzaXplOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgIGYiQ2Fubm90IHNwbGl0IHRoZSBpbnB1dCAne3dvcmtlcl9pbnB1dH0nIG9mIGxlbmd0aCB7bGVuKGlucHV0X2FyZ3VtZW50KX0gdG8ge3NpemV9IHdvcmtlcnMuICIKICAgICAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2UgcmVkdWNlIHRoZSBhbW91bnQgb2Ygd29ya2VycyBmb3IgdGhpcyBpbnB1dC4iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXZlbl9jaHVua19zaXplID0gbGVuKGlucHV0X2FyZ3VtZW50KSAvLyBzaXplCiAgICAgICAgICAgICAgICBjaHVua19zdGFydCA9IHJhbmsgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgIGNodW5rX2VuZCA9ICgKICAgICAgICAgICAgICAgICAgICAocmFuayArIDEpICogZXZlbl9jaHVua19zaXplCiAgICAgICAgICAgICAgICAgICAgaWYgcmFuayArIDEgPCBzaXplCiAgICAgICAgICAgICAgICAgICAgZWxzZSBsZW4oaW5wdXRfYXJndW1lbnQpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKAogICAgICAgICAgICAgICAgICAgIGYiUmFuayAje3Jhbmt9OiBQcm9jZXNzaW5nIGlucHV0IGNodW5rIG9mICd7d29ya2VyX2lucHV0fScgIgogICAgICAgICAgICAgICAgICAgIGYiZnJvbSBpbmRleCB7Y2h1bmtfc3RhcnR9IHRvIHtjaHVua19lbmR9LiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIGxpc3QpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnRbY2h1bmtfc3RhcnQ6Y2h1bmtfZW5kXQogICAgICAgICAgICAgICAgZWxpZiBpc2luc3RhbmNlKGlucHV0X2FyZ3VtZW50LCBwZC5EYXRhRnJhbWUpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gaW5wdXRfYXJndW1lbnQuaWxvY1tjaHVua19zdGFydDpjaHVua19lbmQ6LCA6XQogICAgICAgICAgICAgICAga3dhcmdzW3dvcmtlcl9pbnB1dF0gPSBpbnB1dF9hcmd1bWVudAoKICAgICAgICAgICAgIyBTZXQgdGhlIHJvb3Qgd29ya2VyIG9ubHkgYXJndW1lbnRzOgogICAgICAgICAgICBpZiByYW5rID09IDAgYW5kIHJvb3Rfd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGt3YXJncy51cGRhdGUocm9vdF93b3JrZXJfaW5wdXRzKQoKICAgICAgICAgICAgIyBSdW4gdGhlIHdvcmtlcjoKICAgICAgICAgICAgb3V0cHV0ID0gaGFuZGxlcigqKmt3YXJncykKCiAgICAgICAgICAgICMgU2VuZCB0aGUgb3V0cHV0IHRvIHRoZSByb290IHJhbmsgKHJhbmsgIzApOgogICAgICAgICAgICBvdXRwdXQgPSBjb21tLmdhdGhlcihvdXRwdXQsIHJvb3Q9MCkKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgIyBKb2luIHRoZSBvdXRwdXRzOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQogICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcGQuY29uY2F0KG9ianM9W2RmIGZvciBkZiwgXyBpbiBvdXRwdXRdLCBheGlzPTApCiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZShvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIGVyciBpbiBvdXRwdXRdLCB7fSkKICAgICAgICAgICAgICAgIHJldHVybiBkYXRhZnJhbWUsIGVycm9yc19kaWN0aW9uYXJ5CiAgICAgICAgICAgIHJldHVybiBOb25lCgogICAgICAgIHJldHVybiB3cmFwcGVyCgogICAgcmV0dXJuIGRlY29yYXRvcgoKCkBvcGVuX21waV9oYW5kbGVyKHdvcmtlcl9pbnB1dHM9WyJkYXRhX3BhdGgiXSwgcm9vdF93b3JrZXJfaW5wdXRzPXsidmVyYm9zZSI6IFRydWV9KQpkZWYgYW5zd2VyX3F1ZXN0aW9ucygKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl1dLAogICAgbW9kZWxfbmFtZTogc3RyLAogICAgcXVlc3Rpb25zOiBVbmlvbltMaXN0W3N0cl0sIExpc3RbTGlzdFtzdHJdXV0sCiAgICBkZXZpY2VfbWFwOiBVbmlvbltzdHIsIGRpY3RdID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBhdXRvX2dwdHFfZXhsbGFtYV9tYXhfaW5wdXRfbGVuZ3RoOiBpbnQgPSBOb25lLAogICAgdG9rZW5pemVyX25hbWU6IHN0ciA9IE5vbmUsCiAgICB0b2tlbml6ZXJfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIHRleHRfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBxdWVzdGlvbnNfd3JhcHBlcjogVW5pb25bc3RyLCBMaXN0W3N0cl1dID0gIiIsCiAgICBnZW5lcmF0aW9uX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgcXVlc3Rpb25zX2NvbmZpZzogVW5pb25bRGljdCwgTGlzdFtEaWN0XV0gPSBOb25lLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gMSwKICAgIHF1ZXN0aW9uc19jb2x1bW5zOiBMaXN0W3N0cl0gPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3BkLkRhdGFGcmFtZSwgZGljdF06CiAgICAiIiIKICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbC4gRWFjaCB0ZXh0IGZpbGUgd2lsbCBoYXZlCiAgICB0aGUgZm9sbG93aW5nIHByb21wdCBidWlsdDoKCiAgICBzdGFydCBvZiBgdGV4dF93cmFwcGVyYAogICAgPHRleHQgZmlsZSBjb250ZW50PgogICAgZW5kIG9mIGB0ZXh0X3dyYXBwZXJgCgogICAgc3RhcnQgb2YgYHF1ZXN0aW9uc193cmFwcGVyYAogICAgMS4gPHF1ZXN0aW9uc1swXT4KICAgIDIuIDxxdWVzdGlvbnNbMV0+CiAgICAuLi4KICAgIG4uIDxxdWVzdGlvbnNbbi0xXT4KICAgIGVuZCBvZiBgcXVlc3Rpb25zX3dyYXBwZXJgCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICAgICAgICAgICAgIEEgcGF0aCB0byBhIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgcGF0aCB0byBhIHRleHQgZmlsZSB0byBhc2sKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnMgYWJvdXQuCiAgICA6cGFyYW0gbW9kZWxfbmFtZTogICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHByZS10cmFpbmVkIG1vZGVsIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZSBmb3IgYXNraW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zLgogICAgOnBhcmFtIHF1ZXN0aW9uczogICAgICAgICAgICAgICAgICAgICAgICAgIFRoZSBxdWVzdGlvbnMgdG8gYXNrLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEEgbGlzdCBvZiBsaXN0cyBvZiBxdWVzdGlvbnMgdG8gYXNrIHBlciB0ZXh0IGZpbGUsIGFuZCBkZXZpZGVkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgcXVlc3Rpb24gZ3JvdXBzLCB0aGUgZ3JvdXBzIGNhbiBiZSBkdGVybWFpbmVkIGJ5IHNpemUgKGluIG9yZGVyIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZvaWQgbGFyZ2UgaW5wdXRzIHRvIHRoZSBsbG0pIG9yIGJ5IHF1ZXN0aW9uaW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChyZWd1bGFyIG9yIHBvbGwgbGlrZSBxdWVzdGlvbmluZykuCiAgICA6cGFyYW0gZGV2aWNlX21hcDogICAgICAgICAgICAgICAgICAgICAgICAgQSBtYXAgdG8gdXNlIGZvciBsb2FkaW5nIHRoZSBtb2RlbCBvbiBtdWx0aXBsZSBkZXZpY2VzLgogICAgOnBhcmFtIG1vZGVsX2t3YXJnczogICAgICAgICAgICAgICAgICAgICAgIEtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgZm9yIGxvYWRpbmcgdGhlIG1vZGVsIHVzaW5nIEh1Z2dpbmdGYWNlJ3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZGAgZnVuY3Rpb24uCiAgICA6cGFyYW0gYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDogRm9yIEF1dG9HUFRRIG1vZGVscyB0byBzZXQgYW5kIGV4dGVuZCB0aGUgbW9kZWwncyBpbnB1dCBidWZmZXIgc2l6ZS4KICAgIDpwYXJhbSB0b2tlbml6ZXJfbmFtZTogICAgICAgICAgICAgICAgICAgICBUaGUgdG9rZW5pemVyIG5hbWUgZnJvbSB0aGUgaHVnZ2luZ2ZhY2UgaHViIHRvIHVzZS4gSWYgbm90IGdpdmVuLCB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCBuYW1lIHdpbGwgYmUgdXNlZC4KICAgIDpwYXJhbSB0b2tlbml6ZXJfa3dhcmdzOiAgICAgICAgICAgICAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIGZvciBsb2FkaW5nIHRoZSB0b2tlbml6ZXIgdXNpbmcgSHVnZ2luZ0ZhY2UncwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWRgIGZ1bmN0aW9uLgogICAgOnBhcmFtIHRleHRfd3JhcHBlcjogICAgICAgICAgICAgICAgICAgICAgIEEgd3JhcHBlciBmb3IgdGhlIGZpbGUncyB0ZXh0LiBXaWxsIGJlIGFkZGVkIGF0IHRoZSBzdGFydCBvZiB0aGUgcHJvbXB0LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE11c3QgaGF2ZSBhIHBsYWNlaG9sZGVyICgne30nKSBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUuCiAgICA6cGFyYW0gcXVlc3Rpb25zX3dyYXBwZXI6ICAgICAgICAgICAgICAgICAgQSB3cmFwcGVyIGZvciB0aGUgcXVlc3Rpb25zIHJlY2VpdmVkLiBXaWxsIGJlIGFkZGVkIGFmdGVyIHRoZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JhcHBlciBpbiB0aGUgcHJvbXB0IHRlbXBsYXRlLiBNdXN0IGhhdmUgYSBwbGFjZWhvbGRlciAoJ3t9JykgZm9yIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXN0aW9ucy4KICAgIDpwYXJhbSBnZW5lcmF0aW9uX2NvbmZpZzogICAgICAgICAgICAgICAgICBIdWdnaW5nRmFjZSdzIGBHZW5lcmF0aW9uQ29uZmlnYCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBnZW5lcmF0ZWAgbWV0aG9kLgogICAgOnBhcmFtIHF1ZXN0aW9uc19jb25maWc6ICAgICAgICAgICAgICAgICAgIEEgZGljdGlvbmFyeSBvciBsaXN0IG9mIGRpY3Rpb25hcmllcyBjb250YWluaW5nIHNwZWNpZmljIHdheXMgdG8gYW5zd2VyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlc3Rpb25zICh1c2luZyBhIHBvbGwgZm9yIGV4YW1wbGUpLCBlYWNoIGRpY3Rpb25hcnkgaW4gdGhlIGxpc3QgaXMgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZGluZyBxdWVzdGlvbiBncm91cCBhbmQgZGV0ZXJtaW5lcyB0aGUgcXVlc3Rpb24gYXNraW5nIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzYWlkIGdyb3VwLgogICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgICAgICAgICAgIEJhdGNoIHNpemUgZm9yIGluZmVyZW5jZS4KICAgIDpwYXJhbSBxdWVzdGlvbnNfY29sdW1uczogICAgICAgICAgICAgICAgICBDb2x1bW5zIHRvIHVzZSBmb3IgdGhlIGRhdGFmcmFtZSByZXR1cm5lZC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKCiAgICA6cmV0dXJuczogQSB0dXBsZSBvZjoKCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSBxdWVzdGlvbnMgYW5zd2Vycy4KICAgICAgICAgICAgICAqIEEgZGljdGlvbmFyeSBvZiBlcnJvcmVkIGZpbGVzIHRoYXQgd2VyZSBub3QgaW5mZXJyZWQgb3Igd2VyZSBub3QgYW5zd2VyZWQgcHJvcGVybHkuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBTZXQgY29uZmlncyB0byBlbXB0eSBkaWN0IGlmIG5vdCBnaXZlbjoKICAgIGlmIGdlbmVyYXRpb25fY29uZmlnIGlzIE5vbmU6CiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWcgPSB7fQogICAgaWYgcXVlc3Rpb25zX2NvbmZpZyBpcyBOb25lOgogICAgICAgIHF1ZXN0aW9uc19jb25maWcgPSB7fQoKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHF1ZXN0aW9uOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSBwcm9tcHQgdGVtcGxhdGU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiQ3JlYXRpbmcgcHJvbXB0IHRlbXBsYXRlLiIpCgogICAgIyBPcmdhbml6ZSBxdWVzdGlvbnMgYXMgYSBsaXN0IG9mIGxpc3QsIGFuZCBjb3VudCBudW1iZXIgb2Ygc3ViLWxpc3RzIGZvciBmdXR1cmUgdXNlCiAgICBudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzID0gMSBpZiBpc2luc3RhbmNlKHF1ZXN0aW9uc1swXSwgc3RyKSBlbHNlIGxlbihxdWVzdGlvbnMpCiAgICBxdWVzdGlvbnMgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT1xdWVzdGlvbnMsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIE9yZ2FuaXplIHByb21wdCBwYXJ0cyBhdCBwcm9wZXIgbGVuZ3RoCiAgICB0ZXh0X3dyYXBwZXIgPSBfdG9fZ3JvdXBfbGlzdCgKICAgICAgICBhcmd1bWVudF92YWx1ZT10ZXh0X3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0idGV4dF93cmFwcGVyIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX3dyYXBwZXIsCiAgICAgICAgYXJndW1lbnRfbmFtZT0icXVlc3Rpb25zX3dyYXBwZXIiLAogICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgKQoKICAgICMgQ3JlYXRlIGEgbGlzdCBvZiBwcm9tcHQgYWNjb3JkaW5nIHRvIGdpdmVuIHBhcnRzIGFuZCBxdWVzdGlvbnMKICAgIHByb21wdF90ZW1wbGF0ZSA9IFtdCiAgICBxdWVzdGlvbnMgPSBxdWVzdGlvbnMgaWYgaXNpbnN0YW5jZShxdWVzdGlvbnNbMF0sIGxpc3QpIGVsc2UgW3F1ZXN0aW9uc10KCiAgICAjIEJ1aWxkIGFsbCBwcm9tcHRzCiAgICBmb3IgaSBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICBwcm9tcHRfdGVtcGxhdGUuYXBwZW5kKAogICAgICAgICAgICBfZ2V0X3Byb21wdF90ZW1wbGF0ZSgKICAgICAgICAgICAgICAgIHRleHRfd3JhcHBlcj10ZXh0X3dyYXBwZXJbaV0sCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfd3JhcHBlcj1xdWVzdGlvbnNfd3JhcHBlcltpXSwKICAgICAgICAgICAgICAgIHF1ZXN0aW9ucz1xdWVzdGlvbnNbaV0sCiAgICAgICAgICAgICkKICAgICAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlByb21wdCB0ZW1wbGF0ZSBjcmVhdGVkOlxuXG57cHJvbXB0X3RlbXBsYXRlfVxuIikKCiAgICAjIEdldCB0aGUgdG90YWwgYW1vdW50IG9mIHF1ZXN0aW9uczoKICAgIHF1ZXN0aW9uc19hbW91bnQgPSBzdW0oW2xlbihzdWJsaXN0KSBmb3Igc3VibGlzdCBpbiBxdWVzdGlvbnNdKQoKICAgICMgR2V0IHRoZSBxdWVzdGlvbnMgY29sdW1uczoKICAgIHF1ZXN0aW9uc19jb2x1bW5zID0gcXVlc3Rpb25zX2NvbHVtbnMgb3IgWwogICAgICAgIGYicXtpfSIgZm9yIGkgaW4gcmFuZ2UoMSwgcXVlc3Rpb25zX2Ftb3VudCArIDEpCiAgICBdCgogICAgIyBDaGVjayBpZiB3ZSBoYXZlIHRoZSBjb3JyZWN0IGFtb3VudCBvZiBxdWVzdGlvbnMgY29sdW1uczoKICAgIGlmIGxlbihxdWVzdGlvbnNfY29sdW1ucykgIT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlRoZSBwcm92aWRlZCBxdWVzdGlvbnMgY29sdW1ucyBsZW5ndGggKHtsZW4ocXVlc3Rpb25zX2NvbHVtbnMpfSkgIgogICAgICAgICAgICBmImRvZXMgbm90IG1hdGNoIHRoZSBxdWVzdGlvbnMgYW1vdW50ICh7cXVlc3Rpb25zX2Ftb3VudH0pIgogICAgICAgICkKCiAgICAjIExvYWQgdGhlIGdlbmVyYXRpb24gY29uZmlnOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkxvYWRpbmcgZ2VuZXJhdGlvbiBjb25maWd1cmF0aW9uLiIpCiAgICBnZW5lcmF0aW9uX2NvbmZpZyA9IFsKICAgICAgICB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZygqKihjZmcgb3Ige30pKQogICAgICAgIGZvciBjZmcgaW4gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgICAgIGFyZ3VtZW50X3ZhbHVlPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBhcmd1bWVudF9uYW1lPSJnZW5lcmF0aW9uX2NvbmZpZyIsCiAgICAgICAgICAgIGxlbmd0aD1udW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzLAogICAgICAgICkKICAgIF0KICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiR2VuZXJhdGlvbiBjb25maWd1cmF0aW9uIGxvYWRlZDoge2dlbmVyYXRpb25fY29uZmlnfSIpCgogICAgIyBMb2FkIHRoZSBtb2RlbCBhbmQgdG9rZW5pemVyIGludG8gYSBwaXBlbGluZSBvYmplY3Q6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgbW9kZWwgJ3ttb2RlbF9uYW1lfScuIikKICAgIGdlbmVyYXRpb25fcGlwZWxpbmUgPSBfZ2V0X2dlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIGRldmljZV9tYXA9ZGV2aWNlX21hcCwKICAgICAgICB0b2tlbml6ZXJfbmFtZT10b2tlbml6ZXJfbmFtZSBvciBtb2RlbF9uYW1lLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3Mgb3Ige30sCiAgICAgICAgdG9rZW5pemVyX2t3YXJncz10b2tlbml6ZXJfa3dhcmdzIG9yIHt9LAogICAgICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg9YXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aCwKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiTW9kZWwgbG9hZGVkLiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgU3BsaXQgdGhlIGZpbGVzIGludG8gYmF0Y2hlczoKICAgIGZpbGVfYmF0Y2hlcyA9IFsKICAgICAgICB0ZXh0X2ZpbGVzW2kgOiBpICsgYmF0Y2hfc2l6ZV0KICAgICAgICBpZiBpICsgYmF0Y2hfc2l6ZSA8IGxlbih0ZXh0X2ZpbGVzKQogICAgICAgIGVsc2UgdGV4dF9maWxlc1tpOl0KICAgICAgICBmb3IgaSBpbiByYW5nZSgwLCBsZW4odGV4dF9maWxlcyksIGJhdGNoX3NpemUpCiAgICBdCiAgICBxdWVzdGlvbnNfY29uZmlnID0gX3RvX2dyb3VwX2xpc3QoCiAgICAgICAgYXJndW1lbnRfdmFsdWU9cXVlc3Rpb25zX2NvbmZpZywKICAgICAgICBhcmd1bWVudF9uYW1lPSJxdWVzdGlvbnNfY29uZmlnIiwKICAgICAgICBsZW5ndGg9bnVtYmVyX29mX3F1ZXN0aW9uX2dyb3VwcywKICAgICkKCiAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgcXVlc3Rpb24gaGFuZGxlcnMgYWNjb3JkaW5nIHRvIGdpdmVuIGNvbmZpZ3MKICAgIGhhbmRsZXJzID0gW10KICAgIGZvciBjZmcgaW4gcXVlc3Rpb25zX2NvbmZpZzoKICAgICAgICBxdWVzdGlvbl90eXBlID0gY2ZnLnBvcCgidHlwZSIsICJkZWZhdWx0IikKICAgICAgICBoYW5kbGVycy5hcHBlbmQoUVVFU1RJT05fTUFQUElORy5nZXQocXVlc3Rpb25fdHlwZSkoKipjZmcpKQoKICAgICMgR28gb3ZlciB0aGUgYmF0Y2hlcyBvZiB0ZXh0IGZpbGVzIGFuZCBxdWVzdGlvbiB0aGVtOgogICAgZm9yIGZpbGVfYmF0Y2ggaW4gdHFkbSgKICAgICAgICBmaWxlX2JhdGNoZXMsCiAgICAgICAgZGVzYz0iR2VuZXJhdGluZyBhbnN3ZXJzIiwKICAgICAgICB1bml0PWYiZmlsZSAoYmF0Y2ggb2Yge2JhdGNoX3NpemV9KSIsCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICB0b3RhbF9hbnN3ZXJzID0gW1tdIGZvciBfIGluIHJhbmdlKGJhdGNoX3NpemUpXQoKICAgICAgICAgICAgIyBHbyBvdmVyIGFsbCBxdWVzdGlvbiBncm91cCBwZXIgYmF0Y2ggb2YgZG9jdW1lbnRzCiAgICAgICAgICAgIGZvciBxdWVzdGlvbl9ncm91cCBpbiByYW5nZShudW1iZXJfb2ZfcXVlc3Rpb25fZ3JvdXBzKToKICAgICAgICAgICAgICAgIGN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCA9IGxlbihxdWVzdGlvbnNbcXVlc3Rpb25fZ3JvdXBdKQoKICAgICAgICAgICAgICAgICMgUmVhZCBiYXRjaCAocmVhZCB0aGUgdGV4dCBmcm9tIHRoZSB0ZXh0IGZpbGVzKToKICAgICAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQgPSBfcmVhZF9maWxlX2JhdGNoKAogICAgICAgICAgICAgICAgICAgIGZpbGVfYmF0Y2g9ZmlsZV9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBwcm9tcHRfdGVtcGxhdGU9cHJvbXB0X3RlbXBsYXRlW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIEFuc3dlciB0aGUgcXVlc3Rpb25zIHdpdGggZWFjaCBxdWVzdGlvbiBoYW5kbGVyOgogICAgICAgICAgICAgICAgYmF0Y2hlZF9hbnN3ZXJzID0gaGFuZGxlcnNbcXVlc3Rpb25fZ3JvdXBdLmFuc3dlcigKICAgICAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PWN1cnJlbnRfcXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnW3F1ZXN0aW9uX2dyb3VwXSwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAjIFB1dCB0aGUgYW5zd2VycyBpbiB0aGUgY29ycmVjdCBwbGFjZSBpbiB0aGUgdG90YWwgYW5zd2VycyBsaXN0IGFjY29yZGluZyB0byB0aGUgcGxhY2UgaW4gdGhlIGJhdGNoOgogICAgICAgICAgICAgICAgZm9yIGkgaW4gcmFuZ2UoYmF0Y2hfc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfYW5zd2Vyc1tpXS5leHRlbmQoYmF0Y2hlZF9hbnN3ZXJzW2ldKQoKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXJzIGFuZCBhdHRhY2ggdGhlIGZpbGUgbmFtZToKICAgICAgICAgICAgc3VjY2Vzc2VzLmV4dGVuZCgKICAgICAgICAgICAgICAgIFsKICAgICAgICAgICAgICAgICAgICBbZmlsZS5uYW1lLCAqYW5zd2Vyc10KICAgICAgICAgICAgICAgICAgICBmb3IgZmlsZSwgYW5zd2VycyBpbiB6aXAoZmlsZV9iYXRjaCwgdG90YWxfYW5zd2VycykKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgYmF0Y2hfZmlsZV9uYW1lcyA9ICIsICIuam9pbihbZmlsZS5uYW1lIGZvciBmaWxlIGluIGZpbGVfYmF0Y2hdKQogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKAogICAgICAgICAgICAgICAgICAgIGYiRXJyb3IgaW4gYmF0Y2ggJ3tiYXRjaF9maWxlX25hbWVzfSc6IHtzdHIoZXhjZXB0aW9uKX0iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVycm9yc1tiYXRjaF9maWxlX25hbWVzXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIGFuc3dlcnMgZGF0YWZyYW1lOgogICAgY29sdW1ucyA9IFsKICAgICAgICAidGV4dF9maWxlIiwKICAgICAgICAqcXVlc3Rpb25zX2NvbHVtbnMsCiAgICBdCgogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFuc3dlcnMgYnkgZmlsZXMKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiQW5zd2VycyBzdW1tYXJ5OlxuIgogICAgICAgICAgICBmIntzdWNjZXNzZXMuaGVhZCgpfSIKICAgICAgICApCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMKCgpkZWYgX2dldF90ZXh0X2ZpbGVzKAogICAgZGF0YV9wYXRoOiBwYXRobGliLlBhdGgsCikgLT4gTGlzdFtwYXRobGliLlBhdGhdOgoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgoKICAgICAgICAjIEdldCBhbGwgZmlsZXMgaW5zaWRlIHRoZSBkaXJlY3Rvcnk6CiAgICAgICAgdGV4dF9maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIHRleHRfZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBlaXRoZXIgYSBkaXJlY3RvcnkgcGF0aCBvciBhIGZpbGUgcGF0aC4gIgogICAgICAgICAgICBmIkdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIHRleHRfZmlsZXMKCgpkZWYgX2dldF9wcm9tcHRfdGVtcGxhdGUoCiAgICB0ZXh0X3dyYXBwZXI6IHN0ciwKICAgIHF1ZXN0aW9uc193cmFwcGVyOiBzdHIsCiAgICBxdWVzdGlvbnM6IExpc3Rbc3RyXSwKKSAtPiBzdHI6CgogICAgIyBWYWxpZGF0ZSBhbmQgYnVpbGQgdGhlIHRleHQgd3JhcHBlcjoKICAgIHRleHRfd3JhcHBlciA9IHRleHRfd3JhcHBlciBvciAoCiAgICAgICAgIkdpdmVuIHRoZSBmb2xsb3dpbmcgdGV4dDpcbiIgIi0tLS0tXG4iICJ7fVxuIiAiLS0tLS0iCiAgICApCiAgICBpZiB0ZXh0X3dyYXBwZXIuY291bnQoInt9IikgIT0gMToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAiVGhlIGB0ZXh0X3dyYXBwZXJgIG11c3QgaW5jbHVkZSBvbmUgcGxhY2Vob2xkZXIgJ3t9JyBmb3IgdGhlIHRleHQgb2YgdGhlIGZpbGUgdG8gYmUgYXNrZWQgYWJvdXQuIgogICAgICAgICkKCiAgICAjIFZhbGlkYXRlIGFuZCBidWlsZCB0aGUgcXVlc3Rpb24gd3JhcHBlcjoKICAgIHF1ZXN0aW9uc193cmFwcGVyID0gcXVlc3Rpb25zX3dyYXBwZXIgb3IgIkFuc3dlciB0aGUgcXVlc3Rpb25zOlxuIiAie30iCiAgICBpZiBxdWVzdGlvbnNfd3JhcHBlci5jb3VudCgie30iKSAhPSAxOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICJUaGUgYHF1ZXN0aW9uc193cmFwcGVyYCBtdXN0IGluY2x1ZGUgb25lIHBsYWNlaG9sZGVyICd7fScgZm9yIHRoZSBsaXN0IG9mIHF1ZXN0aW9ucy4iCiAgICAgICAgKQoKICAgICMgVmFsaWRhdGUgYW5kIHBhcnNlIHRoZSBxdWVzdGlvbnM6CiAgICBpZiBsZW4ocXVlc3Rpb25zKSA9PSAwOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlBsZWFzZSBpbmNsdWRlIGF0IGxlYXN0IG9uZSBxdWVzdGlvbi4iKQogICAgcXVlc3Rpb25zID0gIlxuIi5qb2luKAogICAgICAgIFtmIntpfS4ge3F1ZXN0aW9ufSIgZm9yIGksIHF1ZXN0aW9uIGluIGVudW1lcmF0ZShxdWVzdGlvbnMsIDEpXQogICAgKQoKICAgICMgQ29uc3RydWN0IHRoZSB0ZW1wbGF0ZToKICAgIHJldHVybiBmInt0ZXh0X3dyYXBwZXJ9XG57cXVlc3Rpb25zX3dyYXBwZXIuZm9ybWF0KHF1ZXN0aW9ucyl9XG4iCgoKZGVmIF9nZXRfZ2VuZXJhdGlvbl9waXBlbGluZSgKICAgIG1vZGVsX25hbWU6IHN0ciwKICAgIGRldmljZV9tYXA6IFVuaW9uW3N0ciwgZGljdF0sCiAgICB0b2tlbml6ZXJfbmFtZTogc3RyLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0LAogICAgdG9rZW5pemVyX2t3YXJnczogZGljdCwKICAgIGF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGg6IGludCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSAxLAopOgogICAgIyBMb2FkIHRoZSBtb2RlbDoKICAgIG1vZGVsID0gdHJhbnNmb3JtZXJzLkF1dG9Nb2RlbEZvckNhdXNhbExNLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBtb2RlbF9uYW1lLCBkZXZpY2VfbWFwPWRldmljZV9tYXAsICoqbW9kZWxfa3dhcmdzCiAgICApCgogICAgIyBTZXQgZXhsbGFtYSBtYXggaW5wdXQgbGVuZ3RoIGlmIHByb3ZpZGVkOgogICAgIyBUaGlzIGNoYW5nZXMgdGhlIG1vZGVsJ3MgY29udGV4dCBzaXplLgogICAgaWYgYXV0b19ncHRxX2V4bGxhbWFfbWF4X2lucHV0X2xlbmd0aDoKICAgICAgICBmcm9tIGF1dG9fZ3B0cSBpbXBvcnQgZXhsbGFtYV9zZXRfbWF4X2lucHV0X2xlbmd0aAoKICAgICAgICBtb2RlbCA9IGV4bGxhbWFfc2V0X21heF9pbnB1dF9sZW5ndGgoCiAgICAgICAgICAgIG1vZGVsPW1vZGVsLCBtYXhfaW5wdXRfbGVuZ3RoPWF1dG9fZ3B0cV9leGxsYW1hX21heF9pbnB1dF9sZW5ndGgKICAgICAgICApCgogICAgIyBMb2FkIHRoZSB0b2tlbml6ZXI6CiAgICB0b2tlbml6ZXIgPSB0cmFuc2Zvcm1lcnMuQXV0b1Rva2VuaXplci5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgdG9rZW5pemVyX25hbWUsICoqdG9rZW5pemVyX2t3YXJncwogICAgKQoKICAgICMgSW5pdGlhbGl6ZSBhIGdlbmVyYXRpb24gcGlwbGluZSBhbmQgcmV0dXJuOgogICAgcGlwZSA9IHRyYW5zZm9ybWVycy5waXBlbGluZSgKICAgICAgICB0YXNrPSJ0ZXh0LWdlbmVyYXRpb24iLAogICAgICAgIG1vZGVsPW1vZGVsLAogICAgICAgIHRva2VuaXplcj10b2tlbml6ZXIsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplLAogICAgKQogICAgcGlwZS50b2tlbml6ZXIucGFkX3Rva2VuX2lkID0gbW9kZWwuY29uZmlnLmVvc190b2tlbl9pZAogICAgcmV0dXJuIHBpcGUKCgpkZWYgX3JlYWRfZmlsZV9iYXRjaCgKICAgIGZpbGVfYmF0Y2g6IExpc3RbcGF0aGxpYi5QYXRoXSwKICAgIHByb21wdF90ZW1wbGF0ZTogc3RyLAopIC0+IExpc3Rbc3RyXToKICAgIGJhdGNoID0gW10KCiAgICAjIEdvIG92ZXIgYWxsIGZpbGVzIGFuZCByZWFkIGluIHVzYWJsZSBmb3JtYXQKICAgIGZvciBmaWxlIGluIGZpbGVfYmF0Y2g6CiAgICAgICAgd2l0aCBvcGVuKGZpbGUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZnA6CiAgICAgICAgICAgIGJhdGNoLmFwcGVuZChwcm9tcHRfdGVtcGxhdGUuZm9ybWF0KGZwLnJlYWQoKSkpCiAgICByZXR1cm4gYmF0Y2gKCgpkZWYgX3RvX2dyb3VwX2xpc3QoYXJndW1lbnRfdmFsdWU6IGxpc3QsIGFyZ3VtZW50X25hbWU6IHN0ciwgbGVuZ3RoOiBpbnQpOgoKICAgICMgQ2hlY2sgaWYgaXMgbGlzdCwgdHVybiB0byBsaXN0IGlmIG5vdAogICAgYXJndW1lbnRfdmFsdWUgPSAoCiAgICAgICAgYXJndW1lbnRfdmFsdWUgaWYgaXNpbnN0YW5jZShhcmd1bWVudF92YWx1ZSwgbGlzdCkgZWxzZSBbYXJndW1lbnRfdmFsdWVdCiAgICApCiAgICBsaXN0X2xlbiA9IGxlbihhcmd1bWVudF92YWx1ZSkKCiAgICAjIElmIG5vdCBhIGxpc3QsIG9yIGlzIGEgbGlzdCBvZiBsZW4gMSB3ZSBkdXBsaWNhdGUgZm9yIGNvcnJlY3QgbGVuZ3RoCiAgICAjIElmIGxpc3QgaW4gd3JvbmcgbGVuZ3RoIHRocm93IGFuIGVycm9yCiAgICBpZiBsaXN0X2xlbiAhPSBsZW5ndGg6CiAgICAgICAgaWYgbGlzdF9sZW4gPT0gMToKICAgICAgICAgICAgcmV0dXJuIGFyZ3VtZW50X3ZhbHVlICogbGVuZ3RoCiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJUaGUgYXJndW1lbnQgdmFsdWUgb2YgJ3thcmd1bWVudF9uYW1lfScgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIGdpdmVuIHF1ZXN0aW9ucyAtIHtsZW5ndGh9IgogICAgICAgICkKICAgIHJldHVybiBhcmd1bWVudF92YWx1ZQoKCmNsYXNzIFF1ZXN0aW9uSGFuZGxlcjoKICAgICIiIgogICAgQSBjbGFzcyBmb3IgaGFuZGxpbmcgcXVlc3Rpb25zIGFuc3dlcmluZyBmb3IgYSBnaXZlbiBxdWVzdGlvbiB0eXBlLgogICAgVGhpcyBjbGFzcyBpcyB1c2VkIGFzIGEgYmFzZSBjbGFzcyBmb3IgYWxsIHF1ZXN0aW9uIHR5cGVzLCBhbmQgZm9yIGRlZmF1bHQgcXVlc3Rpb24gdHlwZSAocmVndWxhciBxdWVzdGlvbgogICAgYW5zd2VyaW5nIHdpdGhvdXQgYW55IHNwZWNpYWwgaGFuZGxpbmcpLgogICAgIiIiCgogICAgY2xhc3MgQ29uZmlnS2V5czoKICAgICAgICBwYXNzCgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHBhc3MKCiAgICBAc3RhdGljbWV0aG9kCiAgICBkZWYgX2dldF9hbnN3ZXJzKGdlbmVyYXRlZF90ZXh0OiBzdHIsIHF1ZXN0aW9uc19hbW91bnQ6IGludCkgLT4gTGlzdFtzdHJdOgoKICAgICAgICAjIENsZWFyIGFuc3dlciBzdGFydCAocGFydCBiZWZvcmUgbnVtYmVycyk6CiAgICAgICAgIyBUT0RPIGZpbmQgYmV0dGVyIHdheSB0byB2ZXJpZnksIGZvciBsaXN0IG9mIHF1ZXN0aW9ucyB0aGlzIGlzIHJlZHVuZGFudCBmb3IgZXhhbXBsZQogICAgICAgIGlmICIxLiIgbm90IGluIGdlbmVyYXRlZF90ZXh0OgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJBbnN3ZXIgMS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICApCiAgICAgICAgdGV4dCA9IGdlbmVyYXRlZF90ZXh0LnNwbGl0KCIxLiIsIDEpWzFdCgogICAgICAgICMgU3RhcnQgZXh0cmFjdGluZyB0aGUgYW5zd2VyczoKICAgICAgICBhbnN3ZXJzID0gW10KICAgICAgICBmb3IgaSBpbiByYW5nZSgxLCBxdWVzdGlvbnNfYW1vdW50ICsgMSk6CiAgICAgICAgICAgICMgSWYgaXQncyB0aGUgbGFzdCBhbnN3ZXIgdG8gbG9vayBmb3IsIHRha2UgdGhlIHJlc3Qgb2YgdGhlIHRleHQ6CiAgICAgICAgICAgIGlmIGkgPT0gcXVlc3Rpb25zX2Ftb3VudDoKICAgICAgICAgICAgICAgIGFuc3dlcl9pID0gdGV4dAogICAgICAgICAgICAjIFZlcmlmeSB0aGVyZSBpcyBhIHF1ZXN0aW9uIG51bWJlciBpbiB0aGUgdGV4dDoKICAgICAgICAgICAgZWxpZiBmIntpICsgMX0uIiBub3QgaW4gdGV4dDoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJBbnN3ZXIge2kgKyAxfS4gaXMgbWlzc2luZyBmcm9tIHRoZSBnZW5lcmF0ZWQgdGV4dDogJ3tnZW5lcmF0ZWRfdGV4dH0nIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAjIFRha2UgaSdzIGFuc3dlcjoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFuc3dlcl9pLCB0ZXh0ID0gdGV4dC5zcGxpdChmIntpICsgMX0uIiwgMSkKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSBhbnN3ZXIgcmVtb3ZpbmcgcmVkdW5kYW50IHNwYWNlczoKICAgICAgICAgICAgYW5zd2Vycy5hcHBlbmQoYW5zd2VyX2kuc3RyaXAoKSkKCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCiAgICBkZWYgX2luZmVyX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgoKICAgICAgICAjIEluZmVyIHRocm91Z2ggdGhlIGxsbToKICAgICAgICBiYXRjaGVkX291dHB1dCA9IGdlbmVyYXRpb25fcGlwZWxpbmUoCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICAgICBlb3NfdG9rZW5faWQ9Z2VuZXJhdGlvbl9waXBlbGluZS50b2tlbml6ZXIuZW9zX3Rva2VuX2lkLAogICAgICAgICAgICByZXR1cm5fZnVsbF90ZXh0PUZhbHNlLAogICAgICAgICAgICBudW1fcmV0dXJuX3NlcXVlbmNlcz0xLAogICAgICAgICkKCiAgICAgICAgIyBQcm9jZXNzIHRoZSBvdXRwdXRzIHRvIGdldCB0aGUgYW5zd2VyczoKICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2hlZF9vdXRwdXQ6CiAgICAgICAgICAgICMgR2V0IHRoZSBnZW5lcmF0ZWQgYW5zd2VyczoKICAgICAgICAgICAgYW5zd2VycyA9IHNlbGYuX2dldF9hbnN3ZXJzKAogICAgICAgICAgICAgICAgZ2VuZXJhdGVkX3RleHQ9b3V0cHV0WzBdWyJnZW5lcmF0ZWRfdGV4dCJdLAogICAgICAgICAgICAgICAgcXVlc3Rpb25zX2Ftb3VudD1xdWVzdGlvbnNfYW1vdW50LAogICAgICAgICAgICApCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcHJvY2Vzc2VkIGFuc3dlcnM6CiAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VycykKICAgICAgICByZXR1cm4gYmF0Y2hlZF9hbnN3ZXJzCgogICAgZGVmIGFuc3dlcigKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgICIiIgogICAgICAgIEFuc3dlciBxdWVzdGlvbnMgd2l0aCBhIGNvbnRleHQgdG8gdGhlIGdpdmVuIHRleHQgZmlsZXMgY29udGVudHMgYnkgYSBwcmV0cmFpbmVkIExMTSBtb2RlbCBpbiBnaXZlbiBwaXBlbGluZS4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5faW5mZXJfcXVlc3Rpb25zKAogICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgIGJhdGNoZWRfaW5wdXQ9YmF0Y2hlZF9pbnB1dCwKICAgICAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZT1nZW5lcmF0aW9uX3BpcGVsaW5lLAogICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICApCgoKY2xhc3MgUG9sbFF1ZXN0aW9uSGFuZGxlcihRdWVzdGlvbkhhbmRsZXIpOgogICAgIiIiCiAgICBTdGF0aWMgY2xhc3MgdG8gaG9sZCBhbGwgdGhlIHBvc3NpYmxlIHBvbGwgcXVlc3Rpb24gY29uZmlndXJhdGlvbnMgb3B0aW9ucyBrZXlzCiAgICAiIiIKCiAgICBjbGFzcyBDb25maWdLZXlzOgogICAgICAgICIiIgogICAgICAgIEEgY2xhc3MgZm9yIGhhbmRsaW5nIHF1ZXN0aW9ucyBhbnN3ZXJpbmcgZm9yIHBvbGwgdHlwZSBxdWVzdGlvbnMuCiAgICAgICAgVGhlc2UgdHlwZSBvZiBxdWVzdGlvbiBhcmUgYW5zd2VyZWQgYnkgYXNraW5nIHRoZSBzYW1lIHF1ZXN0aW9uIG11bHRpcGxlIHRpbWVzCiAgICAgICAgYW5kIGNob29zaW5nIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgb3IgdGhlIGF2ZXJhZ2UgYW5zd2VyLgogICAgICAgICIiIgoKICAgICAgICAjOiBUaGUgbnVtYmVyIG9mIHRpbWVzIHRvIGFzayB0aGUgc2FtZSBxdWVzdGlvbi4KICAgICAgICBQT0xMX0NPVU5UID0gInBvbGxfY291bnQiCgogICAgICAgICM6IFRoZSBzdHJhdGVneSB0byB1c2UgZm9yIGNob29zaW5nIHRoZSBhbnN3ZXIgZnJvbSB0aGUgcG9sbC4KICAgICAgICBQT0xMX1NUUkFURUdZID0gInBvbGxfc3RyYXRlZ3kiCgogICAgY2xhc3MgU3RyYXRlZ3koZW51bS5FbnVtKToKICAgICAgICAjOiBUaGUgbW9zdCBjb21tb24gYW5zd2VyIHN0cmF0ZWd5LgogICAgICAgIE1PU1RfQ09NTU9OID0gIm1vc3RfY29tbW9uIgoKICAgICAgICAjOiBUaGUgYXZlcmFnZSBhbnN3ZXIgc3RyYXRlZ3kuCiAgICAgICAgQVZFUkFHRSA9ICJhdmVyYWdlIgoKICAgICAgICBAc3RhdGljbWV0aG9kCiAgICAgICAgZGVmIG1vc3RfY29tbW9uKGFuc3dlcnMpOgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgQ2FsY3VsYXRlIHRoZSBtb3N0IGNvbW1vbiBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgY291bnQgPSBDb3VudGVyKGFuc3dlcnMpCiAgICAgICAgICAgIG1vc3RfY29tbW9uID0gY291bnQubW9zdF9jb21tb24oMSkKICAgICAgICAgICAgcmV0dXJuIG1vc3RfY29tbW9uWzBdWzBdCgogICAgICAgIEBzdGF0aWNtZXRob2QKICAgICAgICBkZWYgYXZlcmFnZShhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhbnN3ZXIgZm9yIGEgZ2l2ZW4gbGlzdCBvZiBhbnN3ZXJzLgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShhbnN3ZXJzWzBdLCBzdHIpOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQ2Fubm90IHBlcmZvcm0gcG9sbCB3aXRoIGF2ZXJhZ2UgYW5zd2VyIHN0cmF0ZWd5IG9mIG5vbiBudW1lcmljIHZhbHVlcywiCiAgICAgICAgICAgICAgICAgICAgIiBwbGVhc2UgY2hhbmdlIHRoZSBxdWVzdGlvbiB0byBnaXZlIG51bWVyaWMgZGF0YSwgb3IgY2hvb3NlICdtb3N0X2NvbW1vbicgYXMgc3RyYXRlZ3kuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbnVtZXJpY192YWx1ZXMgPSBhbnN3ZXJzCiAgICAgICAgICAgIGF2ZyA9IHN1bShudW1lcmljX3ZhbHVlcykgLyBsZW4obnVtZXJpY192YWx1ZXMpCgogICAgICAgICAgICAjIFJvdW5kIHRvIHRoZSBjbG9zZXN0IGludGVnZXIgYW5kIHJldHVybiBjb3JyZXNwb25kaW5nIHZhbHVlCiAgICAgICAgICAgIHJldHVybiByb3VuZChhdmcpCgogICAgICAgIGRlZiBkbyhzZWxmLCBhbnN3ZXJzKToKICAgICAgICAgICAgIiIiCiAgICAgICAgICAgIFBlcmZvcm0gdGhlIHN0cmF0ZWd5LgogICAgICAgICAgICAiIiIKICAgICAgICAgICAgcmV0dXJuIGdldGF0dHIoc2VsZiwgc2VsZi52YWx1ZSkoYW5zd2VycykKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgcG9sbF9jb3VudDogaW50ID0gNSwgcG9sbF9zdHJhdGVneTogc3RyID0gIm1vc3RfY29tbW9uIik6CiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygpCiAgICAgICAgc2VsZi5wb2xsX2NvdW50ID0gcG9sbF9jb3VudAogICAgICAgIHNlbGYucG9sbF9zdHJhdGVneSA9IHNlbGYuU3RyYXRlZ3kocG9sbF9zdHJhdGVneSkKCiAgICBkZWYgYW5zd2VyKAogICAgICAgIHNlbGYsCiAgICAgICAgcXVlc3Rpb25zX2Ftb3VudDogaW50LAogICAgICAgIGJhdGNoZWRfaW5wdXQ6IExpc3Rbc3RyXSwKICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICAgICAgZ2VuZXJhdGlvbl9jb25maWc6IHRyYW5zZm9ybWVycy5HZW5lcmF0aW9uQ29uZmlnLAogICAgKSAtPiBMaXN0W0xpc3Rbc3RyXV06CiAgICAgICAgIiIiCiAgICAgICAgQW5zd2VyIHF1ZXN0aW9ucyB3aXRoIGEgY29udGV4dCB0byB0aGUgZ2l2ZW4gdGV4dCBmaWxlcyBjb250ZW50cyBieSBhIHByZXRyYWluZWQgTExNIG1vZGVsIGluIGdpdmVuIHBpcGVsaW5lLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9hbnN3ZXJfcG9sbF9xdWVzdGlvbnMoCiAgICAgICAgICAgIHF1ZXN0aW9uc19hbW91bnQ9cXVlc3Rpb25zX2Ftb3VudCwKICAgICAgICAgICAgYmF0Y2hlZF9pbnB1dD1iYXRjaGVkX2lucHV0LAogICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgIGdlbmVyYXRpb25fY29uZmlnPWdlbmVyYXRpb25fY29uZmlnLAogICAgICAgICkKCiAgICBkZWYgX2Fuc3dlcl9wb2xsX3F1ZXN0aW9ucygKICAgICAgICBzZWxmLAogICAgICAgIHF1ZXN0aW9uc19hbW91bnQ6IGludCwKICAgICAgICBiYXRjaGVkX2lucHV0OiBMaXN0W3N0cl0sCiAgICAgICAgZ2VuZXJhdGlvbl9waXBlbGluZTogdHJhbnNmb3JtZXJzLlBpcGVsaW5lLAogICAgICAgIGdlbmVyYXRpb25fY29uZmlnOiB0cmFuc2Zvcm1lcnMuR2VuZXJhdGlvbkNvbmZpZywKICAgICkgLT4gTGlzdFtMaXN0W3N0cl1dOgogICAgICAgIHZvdGVzID0gW10KCiAgICAgICAgIyBSdW4gdGhlIHBvbGwgZm9yIGVhY2ggcXVlc3Rpb24KICAgICAgICBmb3IgXyBpbiByYW5nZShzZWxmLnBvbGxfY291bnQpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBzZWxmLl9pbmZlcl9xdWVzdGlvbnMoCiAgICAgICAgICAgICAgICBxdWVzdGlvbnNfYW1vdW50PXF1ZXN0aW9uc19hbW91bnQsCiAgICAgICAgICAgICAgICBiYXRjaGVkX2lucHV0PWJhdGNoZWRfaW5wdXQsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX3BpcGVsaW5lPWdlbmVyYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICBnZW5lcmF0aW9uX2NvbmZpZz1nZW5lcmF0aW9uX2NvbmZpZywKICAgICAgICAgICAgKQogICAgICAgICAgICB2b3Rlcy5hcHBlbmQoYmF0Y2hlZF9hbnN3ZXJzKQogICAgICAgIGFuc3dlcnMgPSBbXQoKICAgICAgICAjIENvbGxlY3QgdGhlIGFuc3dlcnMgYWNjb3JkaW5nIHRvIHRoZSBwb2xsIHN0cmF0ZWd5CiAgICAgICAgIyBBdmVyYWdlIHN0cmF0ZWd5IHdvcmtzIGZvciBudW1lcmljIHZhbHVlcyBvbmx5CiAgICAgICAgZm9yIGJhdGNoIGluIHJhbmdlKGxlbih2b3Rlc1swXSkpOgogICAgICAgICAgICBiYXRjaGVkX2Fuc3dlcnMgPSBbXQogICAgICAgICAgICBmb3IgcXVlc3Rpb24gaW4gcmFuZ2UocXVlc3Rpb25zX2Ftb3VudCk6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIGxpc3Qgb2YgYWxsIGFuc3dlcnMgdG8gcmVsZXZhbnQgcXVlc3Rpb24KICAgICAgICAgICAgICAgIGFuc3dlciA9IFsKICAgICAgICAgICAgICAgICAgICB2b3Rlc1t2b3Rlcl1bYmF0Y2hdW3F1ZXN0aW9uXSBmb3Igdm90ZXIgaW4gcmFuZ2Uoc2VsZi5wb2xsX2NvdW50KQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgYW5zd2VyID0gc2VsZi5wb2xsX3N0cmF0ZWd5LmRvKGFuc3dlcikKICAgICAgICAgICAgICAgIGJhdGNoZWRfYW5zd2Vycy5hcHBlbmQoYW5zd2VyKQogICAgICAgICAgICBhbnN3ZXJzLmFwcGVuZChiYXRjaGVkX2Fuc3dlcnMpCiAgICAgICAgcmV0dXJuIGFuc3dlcnMKCgojIEhvbGRzIG5hbWVzIG9mIFF1ZXN0aW9uSGFuZGxlcwpjbGFzcyBRdWVzdGlvblR5cGVzOgogICAgREVGQVVMVCA9ICJkZWZhdWx0IgogICAgUE9MTCA9ICJwb2xsIgoKCiMgTWFwcyBxdWVzdGlvbiB0eXBlcyB0byB0aGVpciBoYW5kbGVycwpRVUVTVElPTl9NQVBQSU5HID0gewogICAgUXVlc3Rpb25UeXBlcy5ERUZBVUxUOiBRdWVzdGlvbkhhbmRsZXIsCiAgICBRdWVzdGlvblR5cGVzLlBPTEw6IFBvbGxRdWVzdGlvbkhhbmRsZXIsCn0K
       entry_points:
         open_mpi_handler:
           name: open_mpi_handler
    +      has_varargs: false
           doc: ''
    +      lineno: 58
           parameters:
           - name: worker_inputs
             type: List[str]
           - name: root_worker_inputs
             type: Dict[str, Any]
             default: null
    -      outputs: []
    -      lineno: 58
    -      has_varargs: false
           has_kwargs: false
         decorator:
           name: decorator
    +      has_varargs: false
           doc: ''
    +      lineno: 66
           parameters:
           - name: handler
    -      outputs: []
    -      lineno: 66
    -      has_varargs: false
           has_kwargs: false
         wrapper:
           name: wrapper
    +      has_varargs: false
           doc: ''
    -      parameters: []
    -      outputs: []
           lineno: 71
    -      has_varargs: false
           has_kwargs: true
         answer_questions:
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[pd.DataFrame, dict]
           name: answer_questions
    +      has_varargs: false
           doc: 'Answer questions with a context to the given text files contents by a
             pretrained LLM model. Each text file will have
     
    @@ -111,6 +104,7 @@
             n. 
     
             end of `questions_wrapper`'
    +      lineno: 130
           parameters:
           - name: data_path
             type: Union[str, List[str]]
    @@ -183,16 +177,15 @@
             type: bool
             doc: 'Whether to present logs of a progress bar and errors. Default: True.'
             default: false
    -      outputs:
    -      - doc: 'A tuple of:'
    -        type: Tuple[pd.DataFrame, dict]
    -      lineno: 130
    -      has_varargs: false
           has_kwargs: false
         answer:
    +      outputs:
    +      - type: List[List[str]]
           name: answer
    +      has_varargs: false
           doc: Answer questions with a context to the given text files contents by a pretrained
             LLM model in given pipeline.
    +      lineno: 674
           parameters:
           - name: self
           - name: questions_amount
    @@ -203,50 +196,35 @@
             type: Pipeline
           - name: generation_config
             type: GenerationConfig
    -      outputs:
    -      - type: List[List[str]]
    -      lineno: 674
    -      has_varargs: false
           has_kwargs: false
         most_common:
           name: most_common
    +      has_varargs: false
           doc: Calculate the most common answer for a given list of answers.
    +      lineno: 637
           parameters:
           - name: answers
    -      outputs: []
    -      lineno: 637
    -      has_varargs: false
           has_kwargs: false
         average:
           name: average
    +      has_varargs: false
           doc: Calculate the average answer for a given list of answers.
    +      lineno: 646
           parameters:
           - name: answers
    -      outputs: []
    -      lineno: 646
    -      has_varargs: false
           has_kwargs: false
         do:
           name: do
    +      has_varargs: false
           doc: Perform the strategy.
    +      lineno: 662
           parameters:
           - name: self
           - name: answers
    -      outputs: []
    -      lineno: 662
    -      has_varargs: false
           has_kwargs: false
    +  image: ''
       description: GenAI approach of question answering on a given data
    -  default_handler: answer_questions
       disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    -verbose: false
     
             
         
    diff --git a/functions/master/question_answering/latest/static/item.html b/functions/master/question_answering/latest/static/item.html index 0baa2db8..64e45da7 100644 --- a/functions/master/question_answering/latest/static/item.html +++ b/functions/master/question_answering/latest/static/item.html @@ -31,8 +31,6 @@ apiVersion: v1 categories: - genai -- huggingface -- machine-learning description: GenAI approach of question answering on a given data doc: '' example: question_answering.ipynb @@ -43,7 +41,7 @@ author: yonish maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: question_answering platformVersion: 3.5.0 spec: @@ -56,7 +54,7 @@ - torch - tqdm url: '' -version: 0.4.0 +version: 0.5.0
    diff --git a/functions/master/question_answering/latest/static/question_answering.html b/functions/master/question_answering/latest/static/question_answering.html index d077dfed..79c5db68 100644 --- a/functions/master/question_answering/latest/static/question_answering.html +++ b/functions/master/question_answering/latest/static/question_answering.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/1.2.0/static/documentation.html b/functions/master/send_email/1.2.0/static/documentation.html index a5315478..ab35e980 100644 --- a/functions/master/send_email/1.2.0/static/documentation.html +++ b/functions/master/send_email/1.2.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/1.2.0/static/example.html b/functions/master/send_email/1.2.0/static/example.html index 752eb14c..4e6a237e 100644 --- a/functions/master/send_email/1.2.0/static/example.html +++ b/functions/master/send_email/1.2.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/1.2.0/static/send_email.html b/functions/master/send_email/1.2.0/static/send_email.html index 6333b120..e806359d 100644 --- a/functions/master/send_email/1.2.0/static/send_email.html +++ b/functions/master/send_email/1.2.0/static/send_email.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/latest/static/documentation.html b/functions/master/send_email/latest/static/documentation.html index a5315478..ab35e980 100644 --- a/functions/master/send_email/latest/static/documentation.html +++ b/functions/master/send_email/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/latest/static/example.html b/functions/master/send_email/latest/static/example.html index 752eb14c..4e6a237e 100644 --- a/functions/master/send_email/latest/static/example.html +++ b/functions/master/send_email/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/send_email/latest/static/send_email.html b/functions/master/send_email/latest/static/send_email.html index 6333b120..e806359d 100644 --- a/functions/master/send_email/latest/static/send_email.html +++ b/functions/master/send_email/latest/static/send_email.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/silero_vad/1.4.0/src/assets/test_data.wav b/functions/master/silero_vad/1.4.0/src/assets/test_data.wav new file mode 100644 index 00000000..a3a993c2 Binary files /dev/null and b/functions/master/silero_vad/1.4.0/src/assets/test_data.wav differ diff --git a/functions/master/silero_vad/1.4.0/src/function.yaml b/functions/master/silero_vad/1.4.0/src/function.yaml new file mode 100644 index 00000000..fd637f1c --- /dev/null +++ b/functions/master/silero_vad/1.4.0/src/function.yaml @@ -0,0 +1,273 @@ +metadata: + tag: '' + categories: + - deep-learning + - audio + name: silero-vad +verbose: false +spec: + description: Silero VAD (Voice Activity Detection) functions. + build: + code_origin: '' + base_image: mlrun/mlrun + requirements: + - torch + - torchaudio + - tqdm + - onnxruntime + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK + origin_filename: '' + image: '' + command: '' + entry_points: + audio_file: + doc: Get the audio file of the task. + lineno: 43 + has_varargs: false + outputs: + - doc: The audio file of the task. + type: Path + parameters: + - name: self + has_kwargs: false + name: audio_file + do_task: + doc: Do the task on the given speech timestamps. The task will diarize the VAD + speech timestamps into speakers. + lineno: 94 + has_varargs: false + parameters: + - name: self + - name: speech_timestamps + type: List[List[Dict[str, int]]] + doc: The speech timestamps per channel to do the task on as outputted from + the VAD. + has_kwargs: false + name: do_task + get_result: + doc: Get the result of the task. A tuple of the audio file name and the result. + lineno: 61 + has_varargs: false + outputs: + - doc: The result of the task. + type: Tuple[str, list] + parameters: + - name: self + has_kwargs: false + name: get_result + to_tuple: + doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing + to pass in queue). + lineno: 116 + has_varargs: false + outputs: + - doc: The converted task. + type: Tuple[str, dict] + parameters: + - name: self + has_kwargs: false + name: to_tuple + create_task: + doc: Create a task with the given audio file. + lineno: 146 + has_varargs: false + outputs: + - doc: The created task. + type: BaseTask + parameters: + - name: self + - name: audio_file + type: Path + doc: The audio file to assign to the task. + has_kwargs: false + name: create_task + from_tuple: + doc: Create a task from a tuple of the audio file name and the task kwargs. + lineno: 157 + has_varargs: false + outputs: + - doc: The created task. + type: BaseTask + parameters: + - name: cls + - name: task_tuple + type: Tuple[str, dict] + doc: The task tuple to create the task from. + has_kwargs: false + name: from_tuple + load: + doc: Load the VAD model. + lineno: 234 + has_varargs: false + parameters: + - name: self + - name: force_reload + type: bool + doc: Whether to force reload the model even if it was already loaded. Default + is True. + default: true + has_kwargs: false + name: load + detect_voice: + doc: "Perform voice activity detection on given audio files using the silero\ + \ VAD model -\nhttps://github.com/snakers4/silero-vad. The end result is a\ + \ dictionary with the file names as keys and their\nVAD timestamps dictionaries\ + \ as value.\n\nFor example::\n\n {\n \"file_1.wav\": [\n \ + \ {\"start\": 0, \"end\": 16000},\n {\"start\": 16000, \"end\"\ + : 32000},\n {\"start\": 32000, \"end\": 48000},\n ...\n\ + \ ],\n \"file_2.wav\": [\n {\"start\": 0, \"end\"\ + : 16000},\n {\"start\": 16000, \"end\": 32000},\n {\"\ + start\": 32000, \"end\": 48000},\n ...\n ],\n ...\n\ + \ }" + lineno: 393 + has_varargs: false + parameters: + - name: data_path + type: Union[str, Path, List[Union[str, Path]]] + doc: The path to the audio files to diarize. Can be a path to a single file, + a path to a directory or a list of paths to files. + - name: use_onnx + type: bool + doc: Whether to use ONNX for inference. Default is True. + default: true + - name: force_onnx_cpu + type: bool + doc: Whether to force ONNX to use CPU for inference. Default is True. + default: true + - name: threshold + type: float + doc: Speech threshold. Silero VAD outputs speech probabilities for each audio + chunk, probabilities ABOVE this value are considered as SPEECH. It is better + to tune this parameter for each dataset separately, but "lazy" 0.5 is pretty + good for most datasets. + default: 0.5 + - name: sampling_rate + type: int + doc: Currently, silero VAD models support 8000 and 16000 sample rates. + default: 16000 + - name: min_speech_duration_ms + type: int + doc: Final speech chunks shorter min_speech_duration_ms are thrown out. + default: 250 + - name: max_speech_duration_s + type: float + doc: Maximum duration of speech chunks in seconds. Chunks longer than `max_speech_duration_s` + will be split at the timestamp of the last silence that lasts more than + 100ms (if any), to prevent aggressive cutting. Otherwise, they will be split + aggressively just before max_speech_duration_s. + default: float('inf') + - name: min_silence_duration_ms + type: int + doc: In the end of each speech chunk wait for min_silence_duration_ms before + separating it. + default: 100 + - name: window_size_samples + type: int + doc: Audio chunks of window_size_samples size are fed to the silero VAD model. + default: 512 + - name: speech_pad_ms + type: int + doc: Final speech chunks are padded by speech_pad_ms each side. + default: 30 + - name: return_seconds + type: bool + doc: Whether return timestamps in seconds. False means to return timestamps + in samples (default - False). + default: false + - name: per_channel + type: bool + doc: Whether to return timestamps per channel (default - False). This will + run VAD on each channel separately and return a list of timestamps per channel. + default: false + - name: use_multiprocessing + type: int + doc: The number of workers to use for multiprocessing. If 0, no multiprocessing + will be used. Default is 0. + default: 0 + - name: verbose + type: bool + doc: Verbosity. + default: false + has_kwargs: false + name: detect_voice + diarize: + doc: "Perform speech diarization on given audio files using the silero VAD model\ + \ - https://github.com/snakers4/silero-vad.\nThe speech diarization is performed\ + \ per channel so that each channel in the audio belong to a different speaker.\ + \ The\nend result is a dictionary with the file names as keys and their diarization\ + \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\ + \nFor example::\n\n {\n \"file_1.wav\": [\n (0.0, 1.0,\ + \ \"speaker_0\"),\n (1.0, 2.0, \"speaker_1\"),\n (2.0,\ + \ 3.0, \"speaker_0\"),\n ...\n ],\n \"file_2.wav\"\ + : [\n (0.0, 1.0, \"speaker_0\"),\n (1.0, 2.0, \"speaker_1\"\ + ),\n (2.0, 3.0, \"speaker_0\"),\n ...\n ],\n\ + \ ...\n }" + lineno: 517 + has_varargs: false + parameters: + - name: data_path + type: Union[str, Path, List[Union[str, Path]]] + doc: The path to the audio files to diarize. Can be a path to a single file, + a path to a directory or a list of paths to files. + - name: use_onnx + type: bool + doc: Whether to use ONNX for inference. Default is True. + default: true + - name: force_onnx_cpu + type: bool + doc: Whether to force ONNX to use CPU for inference. Default is True. + default: true + - name: threshold + type: float + doc: Speech threshold. Silero VAD outputs speech probabilities for each audio + chunk, probabilities ABOVE this value are considered as SPEECH. It is better + to tune this parameter for each dataset separately, but "lazy" 0.5 is pretty + good for most datasets. + default: 0.5 + - name: sampling_rate + type: int + doc: Currently, silero VAD models support 8000 and 16000 sample rates. + default: 16000 + - name: min_speech_duration_ms + type: int + doc: Final speech chunks shorter min_speech_duration_ms are thrown out. + default: 250 + - name: max_speech_duration_s + type: float + doc: Maximum duration of speech chunks in seconds. Chunks longer than `max_speech_duration_s` + will be split at the timestamp of the last silence that lasts more than + 100ms (if any), to prevent aggressive cutting. Otherwise, they will be split + aggressively just before max_speech_duration_s. + default: float('inf') + - name: min_silence_duration_ms + type: int + doc: In the end of each speech chunk wait for min_silence_duration_ms before + separating it. + default: 100 + - name: window_size_samples + type: int + doc: Audio chunks of window_size_samples size are fed to the silero VAD model. + default: 512 + - name: speech_pad_ms + type: int + doc: Final speech chunks are padded by speech_pad_ms each side. + default: 30 + - name: speaker_labels + type: List[str] + doc: The speaker labels to use for the diarization. If not given, the speakers + will be named "speaker_0", "speaker_1", etc. + default: null + - name: use_multiprocessing + type: int + doc: The number of workers to use for multiprocessing. If 0, no multiprocessing + will be used. Default is 0. + default: 0 + - name: verbose + type: bool + doc: Verbosity. + default: false + has_kwargs: false + name: diarize + disable_auto_mount: false + default_handler: detect_voice +kind: job diff --git a/functions/master/silero_vad/1.4.0/src/item.yaml b/functions/master/silero_vad/1.4.0/src/item.yaml new file mode 100644 index 00000000..49adfcd9 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/src/item.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +categories: +- deep-learning +- audio +description: Silero VAD (Voice Activity Detection) functions. +doc: '' +example: silero_vad.ipynb +generationDate: 2023-12-03:14-30 +hidden: false +icon: '' +labels: + author: guyl +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: silero_vad +platformVersion: 3.5.3 +spec: + filename: silero_vad.py + handler: detect_voice + image: mlrun/mlrun + kind: job + requirements: + - torch + - torchaudio + - tqdm + - onnxruntime +url: '' +version: 1.4.0 diff --git a/functions/master/silero_vad/1.4.0/src/silero_vad.ipynb b/functions/master/silero_vad/1.4.0/src/silero_vad.ipynb new file mode 100644 index 00000000..29cd7437 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/src/silero_vad.ipynb @@ -0,0 +1,35 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/silero_vad/1.4.0/src/silero_vad.py b/functions/master/silero_vad/1.4.0/src/silero_vad.py new file mode 100644 index 00000000..a477d4ec --- /dev/null +++ b/functions/master/silero_vad/1.4.0/src/silero_vad.py @@ -0,0 +1,847 @@ +# Copyright 2024 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from multiprocessing import Process, Queue +from pathlib import Path +from types import FunctionType +from typing import Dict, List, Tuple, Type, Union + +import torch +import torchaudio +from tqdm import tqdm + + +class BaseTask: + """ + A base class for a task to complete after VAD. + """ + + def __init__(self, audio_file: Path): + """ + Initialize the base task. + + :param audio_file: The audio file assigned to the task. + """ + # Store the audio file: + self._audio_file = audio_file + + # Prepare the result: + self._result = None + + @property + def audio_file(self) -> Path: + """ + Get the audio file of the task. + + :returns: The audio file of the task. + """ + return self._audio_file + + def do_task( + self, speech_timestamps: Union[List[Dict[str, int]], List[List[Dict[str, int]]]] + ): + """ + Do the task on the given speech timestamps. The base task will simply save the speech timestamps as the result. + + :param speech_timestamps: The speech timestamps to do the task on as outputted from the VAD. + """ + self._result = speech_timestamps + + def get_result(self) -> Tuple[str, list]: + """ + Get the result of the task. A tuple of the audio file name and the result. + + :returns: The result of the task. + """ + return self._audio_file.name, self._result + + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + return self.__class__.__name__, {"audio_file": self._audio_file} + + +class SpeechDiarizationTask(BaseTask): + """ + A speech diarization task. The task will diarize the VAD speech timestamps into speakers. + """ + + def __init__(self, audio_file: Path, speaker_labels: List[str]): + """ + Initialize the speech diarization task. + + :param audio_file: The audio file assigned to the task. + :param speaker_labels: The speaker labels to use for the diarization. If not given, the speakers will be named + "speaker_0", "speaker_1", etc. + """ + super().__init__(audio_file=audio_file) + self._speaker_labels = speaker_labels + + def do_task(self, speech_timestamps: List[List[Dict[str, int]]]): + """ + Do the task on the given speech timestamps. The task will diarize the VAD speech timestamps into speakers. + + :param speech_timestamps: The speech timestamps per channel to do the task on as outputted from the VAD. + """ + # Get the speaker labels (set default if not given): + speaker_labels = self._speaker_labels or [ + f"speaker_{i}" for i in range(len(speech_timestamps)) + ] + + # Diarize - organize the speech timestamps into a single list of speakers and sort it by start time: + speech_diarization = [ + (speech_timestamp["start"], speech_timestamp["end"], speaker_label) + for speaker_label, channel_speech_timestamps in zip( + speaker_labels, speech_timestamps + ) + for speech_timestamp in channel_speech_timestamps + ] + speech_diarization.sort() + self._result = speech_diarization + + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + return task_class, {**task_kwargs, "speaker_labels": self._speaker_labels} + + +class TaskCreator: + """ + A task creator to create different tasks to run after the VAD. + """ + + #: A map from task class name to task class to use in `from_tuple`: + _MAP = { + BaseTask.__name__: BaseTask, + SpeechDiarizationTask.__name__: SpeechDiarizationTask, + } + + def __init__(self, task_type: Type[BaseTask], task_kwargs: dict = None): + """ + Initialize the task creator. + :param task_type: The task type - a `BaseTask` subclass. + :param task_kwargs: Additional keyword arguments to pass to the to be created tasks. + """ + self._task_type = task_type + self._task_kwargs = task_kwargs or {} + + def create_task(self, audio_file: Path) -> BaseTask: + """ + Create a task with the given audio file. + + :param audio_file: The audio file to assign to the task. + + :returns: The created task. + """ + return self._task_type(audio_file=audio_file, **self._task_kwargs) + + @classmethod + def from_tuple(cls, task_tuple: Tuple[str, dict]) -> BaseTask: + """ + Create a task from a tuple of the audio file name and the task kwargs. + + :param task_tuple: The task tuple to create the task from. + + :returns: The created task. + """ + task_class, task_kwargs = task_tuple + return cls._MAP[task_class](**task_kwargs) + + +class VoiceActivityDetector: + """ + A voice activity detection wrapper for the silero VAD model - https://github.com/snakers4/silero-vad. + """ + + def __init__( + self, + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + return_seconds: bool = False, + per_channel: bool = False, + ): + """ + Initialize the voice activity detector. + + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, + they will be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before + separating it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param return_seconds: Whether return timestamps in seconds. False means to return timestamps in + samples (default - False). + :param per_channel: Whether to return timestamps per channel (default - False). This will run VAD + on each channel separately and return a list of timestamps per channel. + """ + # Store configurations: + self._use_onnx = use_onnx + self._force_onnx_cpu = force_onnx_cpu + self._threshold = threshold + self._sampling_rate = sampling_rate + self._min_speech_duration_ms = min_speech_duration_ms + self._max_speech_duration_s = max_speech_duration_s + self._min_silence_duration_ms = min_silence_duration_ms + self._window_size_samples = window_size_samples + self._speech_pad_ms = speech_pad_ms + self._return_seconds = return_seconds + self._per_channel = per_channel + + # Prepare the model variables + self._model: torch.Module = None + self._get_speech_timestamps: FunctionType = None + + def load(self, force_reload: bool = True): + """ + Load the VAD model. + + :param force_reload: Whether to force reload the model even if it was already loaded. Default is True. + """ + model, utils = torch.hub.load( + repo_or_dir="snakers4/silero-vad", + model="silero_vad", + force_reload=force_reload, + onnx=self._use_onnx, + force_onnx_cpu=self._force_onnx_cpu, + ) + self._model = model + ( + self._get_speech_timestamps, + _, # save_audio, + _, # read_audio, + _, # VADIterator, + _, # collect_chunks + ) = utils + + def detect_voice( + self, + audio_file: Path, + ) -> Union[List[Dict[str, int]], List[List[Dict[str, int]]]]: + """ + Infer the audio through the VAD model and return the speech timestamps. + + :param audio_file: The audio file to infer. + + :returns: The speech timestamps in the audio. A list of timestamps where each timestamp is a dictionary with the + following keys: + + * "start": The start sample index of the speech in the audio. + * "end": The end sample index of the speech in the audio. + + If `per_channel` is True, a list of timestamps per channel will be returned. + """ + # Cast to a numpy array: + audio = self._read_audio(audio_file) + + # Detect speech: + if not self._per_channel: + return self._get_speech_timestamps( + audio, + self._model, + threshold=self._threshold, + min_speech_duration_ms=self._min_speech_duration_ms, + max_speech_duration_s=self._max_speech_duration_s, + min_silence_duration_ms=self._min_silence_duration_ms, + speech_pad_ms=self._speech_pad_ms, + sampling_rate=self._sampling_rate, + window_size_samples=self._window_size_samples, + return_seconds=self._return_seconds, + ) + + # Per channel: + speech_timestamps = [] + for channel in audio: + speech_timestamps.append( + self._get_speech_timestamps( + channel, + self._model, + threshold=self._threshold, + min_speech_duration_ms=self._min_speech_duration_ms, + max_speech_duration_s=self._max_speech_duration_s, + min_silence_duration_ms=self._min_silence_duration_ms, + speech_pad_ms=self._speech_pad_ms, + sampling_rate=self._sampling_rate, + window_size_samples=self._window_size_samples, + return_seconds=self._return_seconds, + ) + ) + + return speech_timestamps + + def _read_audio( + self, + path: Path, + ) -> torch.Tensor: + """ + Read the audio from the given path and return it as a tensor. + + :param path: The path to the audio file. + + :returns: The audio as a tensor. + """ + # Read the audio: + audio, sampling_rate = torchaudio.load(str(path)) + + # Check if the audio is stereo and if so, convert it to mono (only if not per channel): + if audio.size(0) > 1 and not self._per_channel: + audio = audio.mean(dim=0, keepdim=True) + + # Resample the audio if needed: + if sampling_rate != self._sampling_rate: + transform = torchaudio.transforms.Resample( + orig_freq=sampling_rate, new_freq=self._sampling_rate + ) + audio = transform(audio) + + # Return the audio (squeeze if not per channel): + return audio if self._per_channel else audio.squeeze(0) + + +#: The value to send into multiprocessing queues to stop the process: +_MULTIPROCESSING_STOP_MARK = "STOP" + + +def _multiprocessing_complete_tasks( + vad_init_kwargs: dict, tasks_queue: Queue, results_queue: Queue +): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param vad_init_kwargs: The VAD initialization kwargs. + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + # Initialize and load the VAD: + vad = VoiceActivityDetector(**vad_init_kwargs) + vad.load(force_reload=False) + + # Start listening to the tasks queue: + while True: + # Get the task: + task: Tuple[str, dict] = tasks_queue.get() + if task == _MULTIPROCESSING_STOP_MARK: + break + try: + # Create the task: + task = TaskCreator.from_tuple(task_tuple=task) + # Run the file through the VAD: + speech_timestamps = vad.detect_voice(audio_file=task.audio_file) + # Complete the task: + task.do_task(speech_timestamps=speech_timestamps) + # Build the result: + result = (False, task.get_result()) + except Exception as exception: + # Build the error: + result = (True, (task.audio_file.name, str(exception))) + # Collect the result / error: + results_queue.put(result) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +# Get the global logger: +try: + import mlrun + + _LOGGER = mlrun.get_or_create_ctx("silero_vad").logger +except ModuleNotFoundError: + _LOGGER = logging.getLogger() + + +def detect_voice( + # Input kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + return_seconds: bool = False, + per_channel: bool = False, + # Other kwargs: + use_multiprocessing: int = 0, + verbose: bool = False, +): + """ + Perform voice activity detection on given audio files using the silero VAD model - + https://github.com/snakers4/silero-vad. The end result is a dictionary with the file names as keys and their + VAD timestamps dictionaries as value. + + For example:: + + { + "file_1.wav": [ + {"start": 0, "end": 16000}, + {"start": 16000, "end": 32000}, + {"start": 32000, "end": 48000}, + ... + ], + "file_2.wav": [ + {"start": 0, "end": 16000}, + {"start": 16000, "end": 32000}, + {"start": 32000, "end": 48000}, + ... + ], + ... + } + + + :param data_path: The path to the audio files to diarize. Can be a path to a single file, a path to a + directory or a list of paths to files. + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will + be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating + it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param return_seconds: Whether return timestamps in seconds. False means to return timestamps in samples + (default - False). + :param per_channel: Whether to return timestamps per channel (default - False). This will run VAD on + each channel separately and return a list of timestamps per channel. + :param use_multiprocessing: The number of workers to use for multiprocessing. If 0, no multiprocessing will + be used. Default is 0. + :param verbose: Verbosity. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Initialize the transcription pipeline: + vad_init_kwargs = { + "use_onnx": use_onnx, + "force_onnx_cpu": force_onnx_cpu, + "threshold": threshold, + "sampling_rate": sampling_rate, + "min_speech_duration_ms": min_speech_duration_ms, + "max_speech_duration_s": max_speech_duration_s, + "min_silence_duration_ms": min_silence_duration_ms, + "window_size_samples": window_size_samples, + "speech_pad_ms": speech_pad_ms, + "return_seconds": return_seconds, + "per_channel": per_channel, + } + + # Create the task creator: + task_creator = TaskCreator(task_type=BaseTask) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Detecting voice", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + description="Detecting voice", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + + # Process the results: + return _process_results(results=results, verbose=verbose) + + +def diarize( + # Input / Output kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + # Diarization kwargs: + speaker_labels: List[str] = None, + # Other kwargs: + use_multiprocessing: int = 0, + verbose: bool = False, +): + """ + Perform speech diarization on given audio files using the silero VAD model - https://github.com/snakers4/silero-vad. + The speech diarization is performed per channel so that each channel in the audio belong to a different speaker. The + end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list + of tuples: (start, end, speaker_label). + + For example:: + + { + "file_1.wav": [ + (0.0, 1.0, "speaker_0"), + (1.0, 2.0, "speaker_1"), + (2.0, 3.0, "speaker_0"), + ... + ], + "file_2.wav": [ + (0.0, 1.0, "speaker_0"), + (1.0, 2.0, "speaker_1"), + (2.0, 3.0, "speaker_0"), + ... + ], + ... + } + + + :param data_path: The path to the audio files to diarize. Can be a path to a single file, a path to a + directory or a list of paths to files. + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will + be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating + it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param speaker_labels: The speaker labels to use for the diarization. If not given, the speakers will be + named "speaker_0", "speaker_1", etc. + :param use_multiprocessing: The number of workers to use for multiprocessing. If 0, no multiprocessing will + be used. Default is 0. + :param verbose: Verbosity. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Initialize the transcription pipeline: + vad_init_kwargs = { + "use_onnx": use_onnx, + "force_onnx_cpu": force_onnx_cpu, + "threshold": threshold, + "sampling_rate": sampling_rate, + "min_speech_duration_ms": min_speech_duration_ms, + "max_speech_duration_s": max_speech_duration_s, + "min_silence_duration_ms": min_silence_duration_ms, + "window_size_samples": window_size_samples, + "speech_pad_ms": speech_pad_ms, + "return_seconds": True, + "per_channel": True, + } + + # Create the task creator: + task_creator = TaskCreator( + task_type=SpeechDiarizationTask, task_kwargs={"speaker_labels": speaker_labels} + ) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Diarizing", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + description="Diarizing", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + + # Process the results: + return _process_results(results=results, verbose=verbose) + + +def _get_audio_files( + data_path: Union[Path, str, list], +) -> List[Path]: + """ + Get the audio files from the data path. If a path to a directory is given, all files in the directory will be + collected. + + :param data_path: The data path to collect the audio files from. + + :returns: The audio files list. + """ + # Check if given a list of paths: + if isinstance(data_path, list): + audio_files = [] + for path in data_path: + audio_files.extend(_get_audio_files(data_path=path)) + return audio_files + + # Check if given a single string path to cast it to a `pathlib.Path`: + if isinstance(data_path, str): + data_path = Path(data_path).absolute() + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a " + f"file. Given: {str(data_path)} " + ) + + return audio_files + + +def _run( + audio_files: List[Path], + description: str, + vad_init_kwargs: dict, + task_creator: TaskCreator, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, list]]]: + """ + Load a VAD and use it to complete the tasks that will be created on the provided files using the given task creator. + + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param vad_init_kwargs: The VAD initialization keyword arguments. + :param task_creator: The task creator to use to create the tasks. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the VAD: + vad = VoiceActivityDetector(**vad_init_kwargs) + if verbose: + _LOGGER.info(f"Loading the VAD model.") + vad.load() + if verbose: + _LOGGER.info("VAD model loaded.") + + # Run the VAD on the audio files and collect the results: + results = [] + for audio_file in tqdm( + audio_files, + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ): + try: + # Create the task: + task = task_creator.create_task(audio_file=audio_file) + # Run the file through the VAD: + speech_timestamps = vad.detect_voice(audio_file=audio_file) + # Complete the task: + task.do_task(speech_timestamps=speech_timestamps) + # Collect the result: + results.append((False, task.get_result())) + except Exception as exception: + # Collect the error: + results.append((True, (audio_file.name, str(exception)))) + + return results + + +def _parallel_run( + n_workers: int, + audio_files: List[Path], + description: str, + vad_init_kwargs: dict, + task_creator: TaskCreator, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, list]]]: + """ + Run multiple VAD workers with multiprocessing to complete the tasks that will be created on the provided files using + the given task creator. + + :param n_workers: The number of workers to use. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param vad_init_kwargs: The VAD initialization keyword arguments. + :param task_creator: The task creator to use to create the tasks. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the VAD (download once, and it will be loaded then per process later on): + if verbose: + _LOGGER.info(f"Loading the VAD model.") + vad = VoiceActivityDetector(**vad_init_kwargs) + vad.load() + if verbose: + _LOGGER.info("VAD model loaded.") + + # Check the number of workers: + if n_workers > len(audio_files): + _LOGGER.warning( + f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). " + f"Setting the number of workers to {len(audio_files)}." + ) + n_workers = len(audio_files) + + # Initialize the multiprocessing queues: + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={ + "vad_init_kwargs": vad_init_kwargs, + "tasks_queue": tasks_queue, + "results_queue": results_queue, + }, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + for p in task_completion_processes: + p.start() + + # Put the tasks in the queue: + for audio_file in audio_files: + tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple()) + + # Put the stop marks in the queue: + for _ in range(n_workers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + # Collect the results: + results = [] + stop_marks_counter = 0 + with tqdm( + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ) as progressbar: + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, list]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + progressbar.update(1) + + # Wait for the processes to finish: + for p in task_completion_processes: + p.join() + + return results + + +def _process_results( + results: List[Tuple[bool, Tuple[str, list]]], verbose: bool +) -> Tuple[dict, dict]: + """ + Process the results of the tasks. + + :param results: The results to process. + :param verbose: Verbosity. + + :returns: The processed results as a tuple of successes and errors. + """ + if verbose: + _LOGGER.info("Summarizing the results.") + successes = {} + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes[result[0]] = result[1] + if verbose: + _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n") + + return successes, errors diff --git a/functions/master/silero_vad/1.4.0/src/test_silero_vad.py b/functions/master/silero_vad/1.4.0/src/test_silero_vad.py new file mode 100644 index 00000000..d46471a5 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/src/test_silero_vad.py @@ -0,0 +1,44 @@ +import os +import tempfile + +import mlrun +import pytest + + +@pytest.fixture() +def setup_test(): + with tempfile.TemporaryDirectory() as artifact_path: + project = mlrun.get_or_create_project(name="default", context=artifact_path) + func = project.set_function( + func=os.path.abspath("./function.yaml"), + name="silero-vad", + image="mlrun/mlrun", + ) + yield func, artifact_path + + +def test_detect_voice(setup_test): + silero_vad_function, artifact_path = setup_test + run = silero_vad_function.run( + handler="detect_voice", + inputs={"data_path": "./assets"}, + returns=["vad_outputs: file", "errors: file"], + artifact_path=artifact_path, + local=True, + ) + assert run.outputs["vad_outputs"] + + +def test_diarize(setup_test): + silero_vad_function, artifact_path = setup_test + run = silero_vad_function.run( + handler="diarize", + inputs={"data_path": "./assets"}, + params={ + "speakers_labels": ["Agent", "Client"], + }, + returns=["speech_diarization: file", "errors: file"], + artifact_path=artifact_path, + local=True, + ) + assert run.outputs["speech_diarization"] diff --git a/functions/master/silero_vad/1.4.0/static/documentation.html b/functions/master/silero_vad/1.4.0/static/documentation.html new file mode 100644 index 00000000..344c2421 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/documentation.html @@ -0,0 +1,541 @@ + + + + + + + +silero_vad package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    silero_vad package#

    +
    +

    Submodules#

    +
    +
    +

    silero_vad.silero_vad module#

    +
    +
    +class silero_vad.silero_vad.BaseTask(audio_file: Path)[source]#
    +

    Bases: object

    +

    A base class for a task to complete after VAD.

    +
    +
    +property audio_file: Path#
    +

    Get the audio file of the task.

    +
    +
    Returns:
    +

    The audio file of the task.

    +
    +
    +
    +
    +
    +do_task(speech_timestamps: List[Dict[str, int]] | List[List[Dict[str, int]]])[source]#
    +

    Do the task on the given speech timestamps. The base task will simply save the speech timestamps as the result.

    +
    +
    Parameters:
    +

    speech_timestamps – The speech timestamps to do the task on as outputted from the VAD.

    +
    +
    +
    +
    +
    +get_result() Tuple[str, list][source]#
    +

    Get the result of the task. A tuple of the audio file name and the result.

    +
    +
    Returns:
    +

    The result of the task.

    +
    +
    +
    +
    +
    +to_tuple() Tuple[str, dict][source]#
    +

    Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).

    +
    +
    Returns:
    +

    The converted task.

    +
    +
    +
    +
    +
    +
    +class silero_vad.silero_vad.SpeechDiarizationTask(audio_file: Path, speaker_labels: List[str])[source]#
    +

    Bases: BaseTask

    +

    A speech diarization task. The task will diarize the VAD speech timestamps into speakers.

    +
    +
    +do_task(speech_timestamps: List[List[Dict[str, int]]])[source]#
    +

    Do the task on the given speech timestamps. The task will diarize the VAD speech timestamps into speakers.

    +
    +
    Parameters:
    +

    speech_timestamps – The speech timestamps per channel to do the task on as outputted from the VAD.

    +
    +
    +
    +
    +
    +to_tuple() Tuple[str, dict][source]#
    +

    Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).

    +
    +
    Returns:
    +

    The converted task.

    +
    +
    +
    +
    +
    +
    +class silero_vad.silero_vad.TaskCreator(task_type: Type[BaseTask], task_kwargs: dict | None = None)[source]#
    +

    Bases: object

    +

    A task creator to create different tasks to run after the VAD.

    +
    +
    +create_task(audio_file: Path) BaseTask[source]#
    +

    Create a task with the given audio file.

    +
    +
    Parameters:
    +

    audio_file – The audio file to assign to the task.

    +
    +
    Returns:
    +

    The created task.

    +
    +
    +
    +
    +
    +classmethod from_tuple(task_tuple: Tuple[str, dict]) BaseTask[source]#
    +

    Create a task from a tuple of the audio file name and the task kwargs.

    +
    +
    Parameters:
    +

    task_tuple – The task tuple to create the task from.

    +
    +
    Returns:
    +

    The created task.

    +
    +
    +
    +
    +
    +
    +class silero_vad.silero_vad.VoiceActivityDetector(use_onnx: bool = True, force_onnx_cpu: bool = True, threshold: float = 0.5, sampling_rate: int = 16000, min_speech_duration_ms: int = 250, max_speech_duration_s: float = inf, min_silence_duration_ms: int = 100, window_size_samples: int = 512, speech_pad_ms: int = 30, return_seconds: bool = False, per_channel: bool = False)[source]#
    +

    Bases: object

    +

    A voice activity detection wrapper for the silero VAD model - snakers4/silero-vad.

    +
    +
    +detect_voice(audio_file: Path) List[Dict[str, int]] | List[List[Dict[str, int]]][source]#
    +

    Infer the audio through the VAD model and return the speech timestamps.

    +
    +
    Parameters:
    +

    audio_file – The audio file to infer.

    +
    +
    Returns:
    +

    The speech timestamps in the audio. A list of timestamps where each timestamp is a dictionary with the +following keys:

    +
      +
    • ”start”: The start sample index of the speech in the audio.

    • +
    • ”end”: The end sample index of the speech in the audio.

    • +
    +

    If per_channel is True, a list of timestamps per channel will be returned.

    +

    +
    +
    +
    +
    +
    +load(force_reload: bool = True)[source]#
    +

    Load the VAD model.

    +
    +
    Parameters:
    +

    force_reload – Whether to force reload the model even if it was already loaded. Default is True.

    +
    +
    +
    +
    +
    +
    +silero_vad.silero_vad.detect_voice(data_path: str | Path | List[str | Path], use_onnx: bool = True, force_onnx_cpu: bool = True, threshold: float = 0.5, sampling_rate: int = 16000, min_speech_duration_ms: int = 250, max_speech_duration_s: float = inf, min_silence_duration_ms: int = 100, window_size_samples: int = 512, speech_pad_ms: int = 30, return_seconds: bool = False, per_channel: bool = False, use_multiprocessing: int = 0, verbose: bool = False)[source]#
    +

    Perform voice activity detection on given audio files using the silero VAD model - +snakers4/silero-vad. The end result is a dictionary with the file names as keys and their +VAD timestamps dictionaries as value.

    +

    For example:

    +
    {
    +    "file_1.wav": [
    +        {"start": 0, "end": 16000},
    +        {"start": 16000, "end": 32000},
    +        {"start": 32000, "end": 48000},
    +        ...
    +    ],
    +    "file_2.wav": [
    +        {"start": 0, "end": 16000},
    +        {"start": 16000, "end": 32000},
    +        {"start": 32000, "end": 48000},
    +        ...
    +    ],
    +    ...
    +}
    +
    +
    +
    +
    Parameters:
    +
      +
    • data_path – The path to the audio files to diarize. Can be a path to a single file, a path to a +directory or a list of paths to files.

    • +
    • use_onnx – Whether to use ONNX for inference. Default is True.

    • +
    • force_onnx_cpu – Whether to force ONNX to use CPU for inference. Default is True.

    • +
    • threshold – Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, +probabilities ABOVE this value are considered as SPEECH. It is better to tune +this parameter for each dataset separately, but “lazy” 0.5 is pretty good for +most datasets.

    • +
    • sampling_rate – Currently, silero VAD models support 8000 and 16000 sample rates.

    • +
    • min_speech_duration_ms – Final speech chunks shorter min_speech_duration_ms are thrown out.

    • +
    • max_speech_duration_s – Maximum duration of speech chunks in seconds. Chunks longer than +max_speech_duration_s will be split at the timestamp of the last silence that +lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will +be split aggressively just before max_speech_duration_s.

    • +
    • min_silence_duration_ms – In the end of each speech chunk wait for min_silence_duration_ms before separating +it.

    • +
    • window_size_samples

      Audio chunks of window_size_samples size are fed to the silero VAD model.

      +

      WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 +sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than +these may affect model performance!

      +

    • +
    • speech_pad_ms – Final speech chunks are padded by speech_pad_ms each side.

    • +
    • return_seconds – Whether return timestamps in seconds. False means to return timestamps in samples +(default - False).

    • +
    • per_channel – Whether to return timestamps per channel (default - False). This will run VAD on +each channel separately and return a list of timestamps per channel.

    • +
    • use_multiprocessing – The number of workers to use for multiprocessing. If 0, no multiprocessing will +be used. Default is 0.

    • +
    • verbose – Verbosity.

    • +
    +
    +
    +
    +
    +
    +silero_vad.silero_vad.diarize(data_path: str | Path | List[str | Path], use_onnx: bool = True, force_onnx_cpu: bool = True, threshold: float = 0.5, sampling_rate: int = 16000, min_speech_duration_ms: int = 250, max_speech_duration_s: float = inf, min_silence_duration_ms: int = 100, window_size_samples: int = 512, speech_pad_ms: int = 30, speaker_labels: List[str] | None = None, use_multiprocessing: int = 0, verbose: bool = False)[source]#
    +

    Perform speech diarization on given audio files using the silero VAD model - snakers4/silero-vad. +The speech diarization is performed per channel so that each channel in the audio belong to a different speaker. The +end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list +of tuples: (start, end, speaker_label).

    +

    For example:

    +
    {
    +    "file_1.wav": [
    +        (0.0, 1.0, "speaker_0"),
    +        (1.0, 2.0, "speaker_1"),
    +        (2.0, 3.0, "speaker_0"),
    +        ...
    +    ],
    +    "file_2.wav": [
    +        (0.0, 1.0, "speaker_0"),
    +        (1.0, 2.0, "speaker_1"),
    +        (2.0, 3.0, "speaker_0"),
    +        ...
    +    ],
    +    ...
    +}
    +
    +
    +
    +
    Parameters:
    +
      +
    • data_path – The path to the audio files to diarize. Can be a path to a single file, a path to a +directory or a list of paths to files.

    • +
    • use_onnx – Whether to use ONNX for inference. Default is True.

    • +
    • force_onnx_cpu – Whether to force ONNX to use CPU for inference. Default is True.

    • +
    • threshold – Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, +probabilities ABOVE this value are considered as SPEECH. It is better to tune +this parameter for each dataset separately, but “lazy” 0.5 is pretty good for +most datasets.

    • +
    • sampling_rate – Currently, silero VAD models support 8000 and 16000 sample rates.

    • +
    • min_speech_duration_ms – Final speech chunks shorter min_speech_duration_ms are thrown out.

    • +
    • max_speech_duration_s – Maximum duration of speech chunks in seconds. Chunks longer than +max_speech_duration_s will be split at the timestamp of the last silence that +lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will +be split aggressively just before max_speech_duration_s.

    • +
    • min_silence_duration_ms – In the end of each speech chunk wait for min_silence_duration_ms before separating +it.

    • +
    • window_size_samples

      Audio chunks of window_size_samples size are fed to the silero VAD model.

      +

      WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 +sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than +these may affect model performance!

      +

    • +
    • speech_pad_ms – Final speech chunks are padded by speech_pad_ms each side.

    • +
    • speaker_labels – The speaker labels to use for the diarization. If not given, the speakers will be +named “speaker_0”, “speaker_1”, etc.

    • +
    • use_multiprocessing – The number of workers to use for multiprocessing. If 0, no multiprocessing will +be used. Default is 0.

    • +
    • verbose – Verbosity.

    • +
    +
    +
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/1.4.0/static/example.html b/functions/master/silero_vad/1.4.0/static/example.html new file mode 100644 index 00000000..0eb7542f --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/example.html @@ -0,0 +1,212 @@ + + + + + + + +<no title> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +

    Contents

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/1.4.0/static/function.html b/functions/master/silero_vad/1.4.0/static/function.html new file mode 100644 index 00000000..839ab544 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/function.html @@ -0,0 +1,308 @@ + + + + + + + + + + + Source + + + + +
    +        
    +metadata:
    +  tag: ''
    +  categories:
    +  - deep-learning
    +  - audio
    +  name: silero-vad
    +verbose: false
    +spec:
    +  description: Silero VAD (Voice Activity Detection) functions.
    +  build:
    +    code_origin: ''
    +    base_image: mlrun/mlrun
    +    requirements:
    +    - torch
    +    - torchaudio
    +    - tqdm
    +    - onnxruntime
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
    +    origin_filename: ''
    +  image: ''
    +  command: ''
    +  entry_points:
    +    audio_file:
    +      doc: Get the audio file of the task.
    +      lineno: 43
    +      has_varargs: false
    +      outputs:
    +      - doc: The audio file of the task.
    +        type: Path
    +      parameters:
    +      - name: self
    +      has_kwargs: false
    +      name: audio_file
    +    do_task:
    +      doc: Do the task on the given speech timestamps. The task will diarize the VAD
    +        speech timestamps into speakers.
    +      lineno: 94
    +      has_varargs: false
    +      parameters:
    +      - name: self
    +      - name: speech_timestamps
    +        type: List[List[Dict[str, int]]]
    +        doc: The speech timestamps per channel to do the task on as outputted from
    +          the VAD.
    +      has_kwargs: false
    +      name: do_task
    +    get_result:
    +      doc: Get the result of the task. A tuple of the audio file name and the result.
    +      lineno: 61
    +      has_varargs: false
    +      outputs:
    +      - doc: The result of the task.
    +        type: Tuple[str, list]
    +      parameters:
    +      - name: self
    +      has_kwargs: false
    +      name: get_result
    +    to_tuple:
    +      doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing
    +        to pass in queue).
    +      lineno: 116
    +      has_varargs: false
    +      outputs:
    +      - doc: The converted task.
    +        type: Tuple[str, dict]
    +      parameters:
    +      - name: self
    +      has_kwargs: false
    +      name: to_tuple
    +    create_task:
    +      doc: Create a task with the given audio file.
    +      lineno: 146
    +      has_varargs: false
    +      outputs:
    +      - doc: The created task.
    +        type: BaseTask
    +      parameters:
    +      - name: self
    +      - name: audio_file
    +        type: Path
    +        doc: The audio file to assign to the task.
    +      has_kwargs: false
    +      name: create_task
    +    from_tuple:
    +      doc: Create a task from a tuple of the audio file name and the task kwargs.
    +      lineno: 157
    +      has_varargs: false
    +      outputs:
    +      - doc: The created task.
    +        type: BaseTask
    +      parameters:
    +      - name: cls
    +      - name: task_tuple
    +        type: Tuple[str, dict]
    +        doc: The task tuple to create the task from.
    +      has_kwargs: false
    +      name: from_tuple
    +    load:
    +      doc: Load the VAD model.
    +      lineno: 234
    +      has_varargs: false
    +      parameters:
    +      - name: self
    +      - name: force_reload
    +        type: bool
    +        doc: Whether to force reload the model even if it was already loaded. Default
    +          is True.
    +        default: true
    +      has_kwargs: false
    +      name: load
    +    detect_voice:
    +      doc: "Perform voice activity detection on given audio files using the silero\
    +        \ VAD model -\nhttps://github.com/snakers4/silero-vad. The end result is a\
    +        \ dictionary with the file names as keys and their\nVAD timestamps dictionaries\
    +        \ as value.\n\nFor example::\n\n    {\n        \"file_1.wav\": [\n       \
    +        \     {\"start\": 0, \"end\": 16000},\n            {\"start\": 16000, \"end\"\
    +        : 32000},\n            {\"start\": 32000, \"end\": 48000},\n            ...\n\
    +        \        ],\n        \"file_2.wav\": [\n            {\"start\": 0, \"end\"\
    +        : 16000},\n            {\"start\": 16000, \"end\": 32000},\n            {\"\
    +        start\": 32000, \"end\": 48000},\n            ...\n        ],\n        ...\n\
    +        \    }"
    +      lineno: 393
    +      has_varargs: false
    +      parameters:
    +      - name: data_path
    +        type: Union[str, Path, List[Union[str, Path]]]
    +        doc: The path to the audio files to diarize. Can be a path to a single file,
    +          a path to a directory or a list of paths to files.
    +      - name: use_onnx
    +        type: bool
    +        doc: Whether to use ONNX for inference. Default is True.
    +        default: true
    +      - name: force_onnx_cpu
    +        type: bool
    +        doc: Whether to force ONNX to use CPU for inference. Default is True.
    +        default: true
    +      - name: threshold
    +        type: float
    +        doc: Speech threshold. Silero VAD outputs speech probabilities for each audio
    +          chunk, probabilities ABOVE this value are considered as SPEECH. It is better
    +          to tune this parameter for each dataset separately, but "lazy" 0.5 is pretty
    +          good for most datasets.
    +        default: 0.5
    +      - name: sampling_rate
    +        type: int
    +        doc: Currently, silero VAD models support 8000 and 16000 sample rates.
    +        default: 16000
    +      - name: min_speech_duration_ms
    +        type: int
    +        doc: Final speech chunks shorter min_speech_duration_ms are thrown out.
    +        default: 250
    +      - name: max_speech_duration_s
    +        type: float
    +        doc: Maximum duration of speech chunks in seconds. Chunks longer than `max_speech_duration_s`
    +          will be split at the timestamp of the last silence that lasts more than
    +          100ms (if any), to prevent aggressive cutting. Otherwise, they will be split
    +          aggressively just before max_speech_duration_s.
    +        default: float('inf')
    +      - name: min_silence_duration_ms
    +        type: int
    +        doc: In the end of each speech chunk wait for min_silence_duration_ms before
    +          separating it.
    +        default: 100
    +      - name: window_size_samples
    +        type: int
    +        doc: Audio chunks of window_size_samples size are fed to the silero VAD model.
    +        default: 512
    +      - name: speech_pad_ms
    +        type: int
    +        doc: Final speech chunks are padded by speech_pad_ms each side.
    +        default: 30
    +      - name: return_seconds
    +        type: bool
    +        doc: Whether return timestamps in seconds. False means to return timestamps
    +          in samples (default - False).
    +        default: false
    +      - name: per_channel
    +        type: bool
    +        doc: Whether to return timestamps per channel (default - False). This will
    +          run VAD on each channel separately and return a list of timestamps per channel.
    +        default: false
    +      - name: use_multiprocessing
    +        type: int
    +        doc: The number of workers to use for multiprocessing. If 0, no multiprocessing
    +          will be used. Default is 0.
    +        default: 0
    +      - name: verbose
    +        type: bool
    +        doc: Verbosity.
    +        default: false
    +      has_kwargs: false
    +      name: detect_voice
    +    diarize:
    +      doc: "Perform speech diarization on given audio files using the silero VAD model\
    +        \ - https://github.com/snakers4/silero-vad.\nThe speech diarization is performed\
    +        \ per channel so that each channel in the audio belong to a different speaker.\
    +        \ The\nend result is a dictionary with the file names as keys and their diarization\
    +        \ as value. A diarization is a list\nof tuples: (start, end, speaker_label).\n\
    +        \nFor example::\n\n    {\n        \"file_1.wav\": [\n            (0.0, 1.0,\
    +        \ \"speaker_0\"),\n            (1.0, 2.0, \"speaker_1\"),\n            (2.0,\
    +        \ 3.0, \"speaker_0\"),\n            ...\n        ],\n        \"file_2.wav\"\
    +        : [\n            (0.0, 1.0, \"speaker_0\"),\n            (1.0, 2.0, \"speaker_1\"\
    +        ),\n            (2.0, 3.0, \"speaker_0\"),\n            ...\n        ],\n\
    +        \        ...\n    }"
    +      lineno: 517
    +      has_varargs: false
    +      parameters:
    +      - name: data_path
    +        type: Union[str, Path, List[Union[str, Path]]]
    +        doc: The path to the audio files to diarize. Can be a path to a single file,
    +          a path to a directory or a list of paths to files.
    +      - name: use_onnx
    +        type: bool
    +        doc: Whether to use ONNX for inference. Default is True.
    +        default: true
    +      - name: force_onnx_cpu
    +        type: bool
    +        doc: Whether to force ONNX to use CPU for inference. Default is True.
    +        default: true
    +      - name: threshold
    +        type: float
    +        doc: Speech threshold. Silero VAD outputs speech probabilities for each audio
    +          chunk, probabilities ABOVE this value are considered as SPEECH. It is better
    +          to tune this parameter for each dataset separately, but "lazy" 0.5 is pretty
    +          good for most datasets.
    +        default: 0.5
    +      - name: sampling_rate
    +        type: int
    +        doc: Currently, silero VAD models support 8000 and 16000 sample rates.
    +        default: 16000
    +      - name: min_speech_duration_ms
    +        type: int
    +        doc: Final speech chunks shorter min_speech_duration_ms are thrown out.
    +        default: 250
    +      - name: max_speech_duration_s
    +        type: float
    +        doc: Maximum duration of speech chunks in seconds. Chunks longer than `max_speech_duration_s`
    +          will be split at the timestamp of the last silence that lasts more than
    +          100ms (if any), to prevent aggressive cutting. Otherwise, they will be split
    +          aggressively just before max_speech_duration_s.
    +        default: float('inf')
    +      - name: min_silence_duration_ms
    +        type: int
    +        doc: In the end of each speech chunk wait for min_silence_duration_ms before
    +          separating it.
    +        default: 100
    +      - name: window_size_samples
    +        type: int
    +        doc: Audio chunks of window_size_samples size are fed to the silero VAD model.
    +        default: 512
    +      - name: speech_pad_ms
    +        type: int
    +        doc: Final speech chunks are padded by speech_pad_ms each side.
    +        default: 30
    +      - name: speaker_labels
    +        type: List[str]
    +        doc: The speaker labels to use for the diarization. If not given, the speakers
    +          will be named "speaker_0", "speaker_1", etc.
    +        default: null
    +      - name: use_multiprocessing
    +        type: int
    +        doc: The number of workers to use for multiprocessing. If 0, no multiprocessing
    +          will be used. Default is 0.
    +        default: 0
    +      - name: verbose
    +        type: bool
    +        doc: Verbosity.
    +        default: false
    +      has_kwargs: false
    +      name: diarize
    +  disable_auto_mount: false
    +  default_handler: detect_voice
    +kind: job
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/1.4.0/static/item.html b/functions/master/silero_vad/1.4.0/static/item.html new file mode 100644 index 00000000..9e297535 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/item.html @@ -0,0 +1,64 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- deep-learning
    +- audio
    +description: Silero VAD (Voice Activity Detection) functions.
    +doc: ''
    +example: silero_vad.ipynb
    +generationDate: 2023-12-03:14-30
    +hidden: false
    +icon: ''
    +labels:
    +  author: guyl
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: silero_vad
    +platformVersion: 3.5.3
    +spec:
    +  filename: silero_vad.py
    +  handler: detect_voice
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +  - torch
    +  - torchaudio
    +  - tqdm
    +  - onnxruntime
    +url: ''
    +version: 1.4.0
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/1.4.0/static/silero_vad.html b/functions/master/silero_vad/1.4.0/static/silero_vad.html new file mode 100644 index 00000000..feae13a6 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/silero_vad.html @@ -0,0 +1,1064 @@ + + + + + + + +silero_vad.silero_vad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for silero_vad.silero_vad

    +# Copyright 2024 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import logging
    +from multiprocessing import Process, Queue
    +from pathlib import Path
    +from types import FunctionType
    +from typing import Dict, List, Tuple, Type, Union
    +
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +
    +
    +
    +[docs] +class BaseTask: + """ + A base class for a task to complete after VAD. + """ + + def __init__(self, audio_file: Path): + """ + Initialize the base task. + + :param audio_file: The audio file assigned to the task. + """ + # Store the audio file: + self._audio_file = audio_file + + # Prepare the result: + self._result = None + + @property + def audio_file(self) -> Path: + """ + Get the audio file of the task. + + :returns: The audio file of the task. + """ + return self._audio_file + +
    +[docs] + def do_task( + self, speech_timestamps: Union[List[Dict[str, int]], List[List[Dict[str, int]]]] + ): + """ + Do the task on the given speech timestamps. The base task will simply save the speech timestamps as the result. + + :param speech_timestamps: The speech timestamps to do the task on as outputted from the VAD. + """ + self._result = speech_timestamps
    + + +
    +[docs] + def get_result(self) -> Tuple[str, list]: + """ + Get the result of the task. A tuple of the audio file name and the result. + + :returns: The result of the task. + """ + return self._audio_file.name, self._result
    + + +
    +[docs] + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + return self.__class__.__name__, {"audio_file": self._audio_file}
    +
    + + + +
    +[docs] +class SpeechDiarizationTask(BaseTask): + """ + A speech diarization task. The task will diarize the VAD speech timestamps into speakers. + """ + + def __init__(self, audio_file: Path, speaker_labels: List[str]): + """ + Initialize the speech diarization task. + + :param audio_file: The audio file assigned to the task. + :param speaker_labels: The speaker labels to use for the diarization. If not given, the speakers will be named + "speaker_0", "speaker_1", etc. + """ + super().__init__(audio_file=audio_file) + self._speaker_labels = speaker_labels + +
    +[docs] + def do_task(self, speech_timestamps: List[List[Dict[str, int]]]): + """ + Do the task on the given speech timestamps. The task will diarize the VAD speech timestamps into speakers. + + :param speech_timestamps: The speech timestamps per channel to do the task on as outputted from the VAD. + """ + # Get the speaker labels (set default if not given): + speaker_labels = self._speaker_labels or [ + f"speaker_{i}" for i in range(len(speech_timestamps)) + ] + + # Diarize - organize the speech timestamps into a single list of speakers and sort it by start time: + speech_diarization = [ + (speech_timestamp["start"], speech_timestamp["end"], speaker_label) + for speaker_label, channel_speech_timestamps in zip( + speaker_labels, speech_timestamps + ) + for speech_timestamp in channel_speech_timestamps + ] + speech_diarization.sort() + self._result = speech_diarization
    + + +
    +[docs] + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + return task_class, {**task_kwargs, "speaker_labels": self._speaker_labels}
    +
    + + + +
    +[docs] +class TaskCreator: + """ + A task creator to create different tasks to run after the VAD. + """ + + #: A map from task class name to task class to use in `from_tuple`: + _MAP = { + BaseTask.__name__: BaseTask, + SpeechDiarizationTask.__name__: SpeechDiarizationTask, + } + + def __init__(self, task_type: Type[BaseTask], task_kwargs: dict = None): + """ + Initialize the task creator. + :param task_type: The task type - a `BaseTask` subclass. + :param task_kwargs: Additional keyword arguments to pass to the to be created tasks. + """ + self._task_type = task_type + self._task_kwargs = task_kwargs or {} + +
    +[docs] + def create_task(self, audio_file: Path) -> BaseTask: + """ + Create a task with the given audio file. + + :param audio_file: The audio file to assign to the task. + + :returns: The created task. + """ + return self._task_type(audio_file=audio_file, **self._task_kwargs)
    + + +
    +[docs] + @classmethod + def from_tuple(cls, task_tuple: Tuple[str, dict]) -> BaseTask: + """ + Create a task from a tuple of the audio file name and the task kwargs. + + :param task_tuple: The task tuple to create the task from. + + :returns: The created task. + """ + task_class, task_kwargs = task_tuple + return cls._MAP[task_class](**task_kwargs)
    +
    + + + +
    +[docs] +class VoiceActivityDetector: + """ + A voice activity detection wrapper for the silero VAD model - https://github.com/snakers4/silero-vad. + """ + + def __init__( + self, + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + return_seconds: bool = False, + per_channel: bool = False, + ): + """ + Initialize the voice activity detector. + + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, + they will be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before + separating it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param return_seconds: Whether return timestamps in seconds. False means to return timestamps in + samples (default - False). + :param per_channel: Whether to return timestamps per channel (default - False). This will run VAD + on each channel separately and return a list of timestamps per channel. + """ + # Store configurations: + self._use_onnx = use_onnx + self._force_onnx_cpu = force_onnx_cpu + self._threshold = threshold + self._sampling_rate = sampling_rate + self._min_speech_duration_ms = min_speech_duration_ms + self._max_speech_duration_s = max_speech_duration_s + self._min_silence_duration_ms = min_silence_duration_ms + self._window_size_samples = window_size_samples + self._speech_pad_ms = speech_pad_ms + self._return_seconds = return_seconds + self._per_channel = per_channel + + # Prepare the model variables + self._model: torch.Module = None + self._get_speech_timestamps: FunctionType = None + +
    +[docs] + def load(self, force_reload: bool = True): + """ + Load the VAD model. + + :param force_reload: Whether to force reload the model even if it was already loaded. Default is True. + """ + model, utils = torch.hub.load( + repo_or_dir="snakers4/silero-vad", + model="silero_vad", + force_reload=force_reload, + onnx=self._use_onnx, + force_onnx_cpu=self._force_onnx_cpu, + ) + self._model = model + ( + self._get_speech_timestamps, + _, # save_audio, + _, # read_audio, + _, # VADIterator, + _, # collect_chunks + ) = utils
    + + +
    +[docs] + def detect_voice( + self, + audio_file: Path, + ) -> Union[List[Dict[str, int]], List[List[Dict[str, int]]]]: + """ + Infer the audio through the VAD model and return the speech timestamps. + + :param audio_file: The audio file to infer. + + :returns: The speech timestamps in the audio. A list of timestamps where each timestamp is a dictionary with the + following keys: + + * "start": The start sample index of the speech in the audio. + * "end": The end sample index of the speech in the audio. + + If `per_channel` is True, a list of timestamps per channel will be returned. + """ + # Cast to a numpy array: + audio = self._read_audio(audio_file) + + # Detect speech: + if not self._per_channel: + return self._get_speech_timestamps( + audio, + self._model, + threshold=self._threshold, + min_speech_duration_ms=self._min_speech_duration_ms, + max_speech_duration_s=self._max_speech_duration_s, + min_silence_duration_ms=self._min_silence_duration_ms, + speech_pad_ms=self._speech_pad_ms, + sampling_rate=self._sampling_rate, + window_size_samples=self._window_size_samples, + return_seconds=self._return_seconds, + ) + + # Per channel: + speech_timestamps = [] + for channel in audio: + speech_timestamps.append( + self._get_speech_timestamps( + channel, + self._model, + threshold=self._threshold, + min_speech_duration_ms=self._min_speech_duration_ms, + max_speech_duration_s=self._max_speech_duration_s, + min_silence_duration_ms=self._min_silence_duration_ms, + speech_pad_ms=self._speech_pad_ms, + sampling_rate=self._sampling_rate, + window_size_samples=self._window_size_samples, + return_seconds=self._return_seconds, + ) + ) + + return speech_timestamps
    + + + def _read_audio( + self, + path: Path, + ) -> torch.Tensor: + """ + Read the audio from the given path and return it as a tensor. + + :param path: The path to the audio file. + + :returns: The audio as a tensor. + """ + # Read the audio: + audio, sampling_rate = torchaudio.load(str(path)) + + # Check if the audio is stereo and if so, convert it to mono (only if not per channel): + if audio.size(0) > 1 and not self._per_channel: + audio = audio.mean(dim=0, keepdim=True) + + # Resample the audio if needed: + if sampling_rate != self._sampling_rate: + transform = torchaudio.transforms.Resample( + orig_freq=sampling_rate, new_freq=self._sampling_rate + ) + audio = transform(audio) + + # Return the audio (squeeze if not per channel): + return audio if self._per_channel else audio.squeeze(0)
    + + + +#: The value to send into multiprocessing queues to stop the process: +_MULTIPROCESSING_STOP_MARK = "STOP" + + +def _multiprocessing_complete_tasks( + vad_init_kwargs: dict, tasks_queue: Queue, results_queue: Queue +): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param vad_init_kwargs: The VAD initialization kwargs. + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + # Initialize and load the VAD: + vad = VoiceActivityDetector(**vad_init_kwargs) + vad.load(force_reload=False) + + # Start listening to the tasks queue: + while True: + # Get the task: + task: Tuple[str, dict] = tasks_queue.get() + if task == _MULTIPROCESSING_STOP_MARK: + break + try: + # Create the task: + task = TaskCreator.from_tuple(task_tuple=task) + # Run the file through the VAD: + speech_timestamps = vad.detect_voice(audio_file=task.audio_file) + # Complete the task: + task.do_task(speech_timestamps=speech_timestamps) + # Build the result: + result = (False, task.get_result()) + except Exception as exception: + # Build the error: + result = (True, (task.audio_file.name, str(exception))) + # Collect the result / error: + results_queue.put(result) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +# Get the global logger: +try: + import mlrun + + _LOGGER = mlrun.get_or_create_ctx("silero_vad").logger +except ModuleNotFoundError: + _LOGGER = logging.getLogger() + + +
    +[docs] +def detect_voice( + # Input kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + return_seconds: bool = False, + per_channel: bool = False, + # Other kwargs: + use_multiprocessing: int = 0, + verbose: bool = False, +): + """ + Perform voice activity detection on given audio files using the silero VAD model - + https://github.com/snakers4/silero-vad. The end result is a dictionary with the file names as keys and their + VAD timestamps dictionaries as value. + + For example:: + + { + "file_1.wav": [ + {"start": 0, "end": 16000}, + {"start": 16000, "end": 32000}, + {"start": 32000, "end": 48000}, + ... + ], + "file_2.wav": [ + {"start": 0, "end": 16000}, + {"start": 16000, "end": 32000}, + {"start": 32000, "end": 48000}, + ... + ], + ... + } + + + :param data_path: The path to the audio files to diarize. Can be a path to a single file, a path to a + directory or a list of paths to files. + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will + be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating + it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param return_seconds: Whether return timestamps in seconds. False means to return timestamps in samples + (default - False). + :param per_channel: Whether to return timestamps per channel (default - False). This will run VAD on + each channel separately and return a list of timestamps per channel. + :param use_multiprocessing: The number of workers to use for multiprocessing. If 0, no multiprocessing will + be used. Default is 0. + :param verbose: Verbosity. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Initialize the transcription pipeline: + vad_init_kwargs = { + "use_onnx": use_onnx, + "force_onnx_cpu": force_onnx_cpu, + "threshold": threshold, + "sampling_rate": sampling_rate, + "min_speech_duration_ms": min_speech_duration_ms, + "max_speech_duration_s": max_speech_duration_s, + "min_silence_duration_ms": min_silence_duration_ms, + "window_size_samples": window_size_samples, + "speech_pad_ms": speech_pad_ms, + "return_seconds": return_seconds, + "per_channel": per_channel, + } + + # Create the task creator: + task_creator = TaskCreator(task_type=BaseTask) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Detecting voice", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + description="Detecting voice", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + + # Process the results: + return _process_results(results=results, verbose=verbose)
    + + + +
    +[docs] +def diarize( + # Input / Output kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + # Model loading kwargs: + use_onnx: bool = True, + force_onnx_cpu: bool = True, + # Detection kwargs: + threshold: float = 0.5, + sampling_rate: int = 16_000, + min_speech_duration_ms: int = 250, + max_speech_duration_s: float = float("inf"), + min_silence_duration_ms: int = 100, + window_size_samples: int = 512, + speech_pad_ms: int = 30, + # Diarization kwargs: + speaker_labels: List[str] = None, + # Other kwargs: + use_multiprocessing: int = 0, + verbose: bool = False, +): + """ + Perform speech diarization on given audio files using the silero VAD model - https://github.com/snakers4/silero-vad. + The speech diarization is performed per channel so that each channel in the audio belong to a different speaker. The + end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list + of tuples: (start, end, speaker_label). + + For example:: + + { + "file_1.wav": [ + (0.0, 1.0, "speaker_0"), + (1.0, 2.0, "speaker_1"), + (2.0, 3.0, "speaker_0"), + ... + ], + "file_2.wav": [ + (0.0, 1.0, "speaker_0"), + (1.0, 2.0, "speaker_1"), + (2.0, 3.0, "speaker_0"), + ... + ], + ... + } + + + :param data_path: The path to the audio files to diarize. Can be a path to a single file, a path to a + directory or a list of paths to files. + :param use_onnx: Whether to use ONNX for inference. Default is True. + :param force_onnx_cpu: Whether to force ONNX to use CPU for inference. Default is True. + :param threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk, + probabilities ABOVE this value are considered as SPEECH. It is better to tune + this parameter for each dataset separately, but "lazy" 0.5 is pretty good for + most datasets. + :param sampling_rate: Currently, silero VAD models support 8000 and 16000 sample rates. + :param min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out. + :param max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer than + `max_speech_duration_s` will be split at the timestamp of the last silence that + lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will + be split aggressively just before max_speech_duration_s. + :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating + it. + :param window_size_samples: Audio chunks of window_size_samples size are fed to the silero VAD model. + + WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000 + sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than + these may affect model performance! + :param speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side. + :param speaker_labels: The speaker labels to use for the diarization. If not given, the speakers will be + named "speaker_0", "speaker_1", etc. + :param use_multiprocessing: The number of workers to use for multiprocessing. If 0, no multiprocessing will + be used. Default is 0. + :param verbose: Verbosity. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Initialize the transcription pipeline: + vad_init_kwargs = { + "use_onnx": use_onnx, + "force_onnx_cpu": force_onnx_cpu, + "threshold": threshold, + "sampling_rate": sampling_rate, + "min_speech_duration_ms": min_speech_duration_ms, + "max_speech_duration_s": max_speech_duration_s, + "min_silence_duration_ms": min_silence_duration_ms, + "window_size_samples": window_size_samples, + "speech_pad_ms": speech_pad_ms, + "return_seconds": True, + "per_channel": True, + } + + # Create the task creator: + task_creator = TaskCreator( + task_type=SpeechDiarizationTask, task_kwargs={"speaker_labels": speaker_labels} + ) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing, + audio_files=audio_files, + description="Diarizing", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + description="Diarizing", + vad_init_kwargs=vad_init_kwargs, + task_creator=task_creator, + verbose=verbose, + ) + + # Process the results: + return _process_results(results=results, verbose=verbose)
    + + + +def _get_audio_files( + data_path: Union[Path, str, list], +) -> List[Path]: + """ + Get the audio files from the data path. If a path to a directory is given, all files in the directory will be + collected. + + :param data_path: The data path to collect the audio files from. + + :returns: The audio files list. + """ + # Check if given a list of paths: + if isinstance(data_path, list): + audio_files = [] + for path in data_path: + audio_files.extend(_get_audio_files(data_path=path)) + return audio_files + + # Check if given a single string path to cast it to a `pathlib.Path`: + if isinstance(data_path, str): + data_path = Path(data_path).absolute() + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a " + f"file. Given: {str(data_path)} " + ) + + return audio_files + + +def _run( + audio_files: List[Path], + description: str, + vad_init_kwargs: dict, + task_creator: TaskCreator, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, list]]]: + """ + Load a VAD and use it to complete the tasks that will be created on the provided files using the given task creator. + + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param vad_init_kwargs: The VAD initialization keyword arguments. + :param task_creator: The task creator to use to create the tasks. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the VAD: + vad = VoiceActivityDetector(**vad_init_kwargs) + if verbose: + _LOGGER.info(f"Loading the VAD model.") + vad.load() + if verbose: + _LOGGER.info("VAD model loaded.") + + # Run the VAD on the audio files and collect the results: + results = [] + for audio_file in tqdm( + audio_files, + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ): + try: + # Create the task: + task = task_creator.create_task(audio_file=audio_file) + # Run the file through the VAD: + speech_timestamps = vad.detect_voice(audio_file=audio_file) + # Complete the task: + task.do_task(speech_timestamps=speech_timestamps) + # Collect the result: + results.append((False, task.get_result())) + except Exception as exception: + # Collect the error: + results.append((True, (audio_file.name, str(exception)))) + + return results + + +def _parallel_run( + n_workers: int, + audio_files: List[Path], + description: str, + vad_init_kwargs: dict, + task_creator: TaskCreator, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, list]]]: + """ + Run multiple VAD workers with multiprocessing to complete the tasks that will be created on the provided files using + the given task creator. + + :param n_workers: The number of workers to use. + :param audio_files: The audio files to use. + :param description: The description to use for the progress bar. + :param vad_init_kwargs: The VAD initialization keyword arguments. + :param task_creator: The task creator to use to create the tasks. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the VAD (download once, and it will be loaded then per process later on): + if verbose: + _LOGGER.info(f"Loading the VAD model.") + vad = VoiceActivityDetector(**vad_init_kwargs) + vad.load() + if verbose: + _LOGGER.info("VAD model loaded.") + + # Check the number of workers: + if n_workers > len(audio_files): + _LOGGER.warning( + f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). " + f"Setting the number of workers to {len(audio_files)}." + ) + n_workers = len(audio_files) + + # Initialize the multiprocessing queues: + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={ + "vad_init_kwargs": vad_init_kwargs, + "tasks_queue": tasks_queue, + "results_queue": results_queue, + }, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + for p in task_completion_processes: + p.start() + + # Put the tasks in the queue: + for audio_file in audio_files: + tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple()) + + # Put the stop marks in the queue: + for _ in range(n_workers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + # Collect the results: + results = [] + stop_marks_counter = 0 + with tqdm( + desc=description, + unit="file", + total=len(audio_files), + disable=not verbose, + ) as progressbar: + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, list]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + progressbar.update(1) + + # Wait for the processes to finish: + for p in task_completion_processes: + p.join() + + return results + + +def _process_results( + results: List[Tuple[bool, Tuple[str, list]]], verbose: bool +) -> Tuple[dict, dict]: + """ + Process the results of the tasks. + + :param results: The results to process. + :param verbose: Verbosity. + + :returns: The processed results as a tuple of successes and errors. + """ + if verbose: + _LOGGER.info("Summarizing the results.") + successes = {} + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes[result[0]] = result[1] + if verbose: + _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n") + + return successes, errors +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/1.4.0/static/source.html b/functions/master/silero_vad/1.4.0/static/source.html new file mode 100644 index 00000000..6a255822 --- /dev/null +++ b/functions/master/silero_vad/1.4.0/static/source.html @@ -0,0 +1,882 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2024 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import logging
    +from multiprocessing import Process, Queue
    +from pathlib import Path
    +from types import FunctionType
    +from typing import Dict, List, Tuple, Type, Union
    +
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +
    +
    +class BaseTask:
    +    """
    +    A base class for a task to complete after VAD.
    +    """
    +
    +    def __init__(self, audio_file: Path):
    +        """
    +        Initialize the base task.
    +
    +        :param audio_file: The audio file assigned to the task.
    +        """
    +        # Store the audio file:
    +        self._audio_file = audio_file
    +
    +        # Prepare the result:
    +        self._result = None
    +
    +    @property
    +    def audio_file(self) -> Path:
    +        """
    +        Get the audio file of the task.
    +
    +        :returns: The audio file of the task.
    +        """
    +        return self._audio_file
    +
    +    def do_task(
    +        self, speech_timestamps: Union[List[Dict[str, int]], List[List[Dict[str, int]]]]
    +    ):
    +        """
    +        Do the task on the given speech timestamps. The base task will simply save the speech timestamps as the result.
    +
    +        :param speech_timestamps: The speech timestamps to do the task on as outputted from the VAD.
    +        """
    +        self._result = speech_timestamps
    +
    +    def get_result(self) -> Tuple[str, list]:
    +        """
    +        Get the result of the task. A tuple of the audio file name and the result.
    +
    +        :returns: The result of the task.
    +        """
    +        return self._audio_file.name, self._result
    +
    +    def to_tuple(self) -> Tuple[str, dict]:
    +        """
    +        Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).
    +
    +        :returns: The converted task.
    +        """
    +        return self.__class__.__name__, {"audio_file": self._audio_file}
    +
    +
    +class SpeechDiarizationTask(BaseTask):
    +    """
    +    A speech diarization task. The task will diarize the VAD speech timestamps into speakers.
    +    """
    +
    +    def __init__(self, audio_file: Path, speaker_labels: List[str]):
    +        """
    +        Initialize the speech diarization task.
    +
    +        :param audio_file:     The audio file assigned to the task.
    +        :param speaker_labels: The speaker labels to use for the diarization. If not given, the speakers will be named
    +                               "speaker_0", "speaker_1", etc.
    +        """
    +        super().__init__(audio_file=audio_file)
    +        self._speaker_labels = speaker_labels
    +
    +    def do_task(self, speech_timestamps: List[List[Dict[str, int]]]):
    +        """
    +        Do the task on the given speech timestamps. The task will diarize the VAD speech timestamps into speakers.
    +
    +        :param speech_timestamps: The speech timestamps per channel to do the task on as outputted from the VAD.
    +        """
    +        # Get the speaker labels (set default if not given):
    +        speaker_labels = self._speaker_labels or [
    +            f"speaker_{i}" for i in range(len(speech_timestamps))
    +        ]
    +
    +        # Diarize - organize the speech timestamps into a single list of speakers and sort it by start time:
    +        speech_diarization = [
    +            (speech_timestamp["start"], speech_timestamp["end"], speaker_label)
    +            for speaker_label, channel_speech_timestamps in zip(
    +                speaker_labels, speech_timestamps
    +            )
    +            for speech_timestamp in channel_speech_timestamps
    +        ]
    +        speech_diarization.sort()
    +        self._result = speech_diarization
    +
    +    def to_tuple(self) -> Tuple[str, dict]:
    +        """
    +        Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).
    +
    +        :returns: The converted task.
    +        """
    +        task_class, task_kwargs = super().to_tuple()
    +        return task_class, {**task_kwargs, "speaker_labels": self._speaker_labels}
    +
    +
    +class TaskCreator:
    +    """
    +    A task creator to create different tasks to run after the VAD.
    +    """
    +
    +    #: A map from task class name to task class to use in `from_tuple`:
    +    _MAP = {
    +        BaseTask.__name__: BaseTask,
    +        SpeechDiarizationTask.__name__: SpeechDiarizationTask,
    +    }
    +
    +    def __init__(self, task_type: Type[BaseTask], task_kwargs: dict = None):
    +        """
    +        Initialize the task creator.
    +        :param task_type: The task type - a `BaseTask` subclass.
    +        :param task_kwargs: Additional keyword arguments to pass to the to be created tasks.
    +        """
    +        self._task_type = task_type
    +        self._task_kwargs = task_kwargs or {}
    +
    +    def create_task(self, audio_file: Path) -> BaseTask:
    +        """
    +        Create a task with the given audio file.
    +
    +        :param audio_file: The audio file to assign to the task.
    +
    +        :returns: The created task.
    +        """
    +        return self._task_type(audio_file=audio_file, **self._task_kwargs)
    +
    +    @classmethod
    +    def from_tuple(cls, task_tuple: Tuple[str, dict]) -> BaseTask:
    +        """
    +        Create a task from a tuple of the audio file name and the task kwargs.
    +
    +        :param task_tuple: The task tuple to create the task from.
    +
    +        :returns: The created task.
    +        """
    +        task_class, task_kwargs = task_tuple
    +        return cls._MAP[task_class](**task_kwargs)
    +
    +
    +class VoiceActivityDetector:
    +    """
    +    A voice activity detection wrapper for the silero VAD model - https://github.com/snakers4/silero-vad.
    +    """
    +
    +    def __init__(
    +        self,
    +        # Model loading kwargs:
    +        use_onnx: bool = True,
    +        force_onnx_cpu: bool = True,
    +        # Detection kwargs:
    +        threshold: float = 0.5,
    +        sampling_rate: int = 16_000,
    +        min_speech_duration_ms: int = 250,
    +        max_speech_duration_s: float = float("inf"),
    +        min_silence_duration_ms: int = 100,
    +        window_size_samples: int = 512,
    +        speech_pad_ms: int = 30,
    +        return_seconds: bool = False,
    +        per_channel: bool = False,
    +    ):
    +        """
    +        Initialize the voice activity detector.
    +
    +        :param use_onnx:                Whether to use ONNX for inference. Default is True.
    +        :param force_onnx_cpu:          Whether to force ONNX to use CPU for inference. Default is True.
    +        :param threshold:               Speech threshold. Silero VAD outputs speech probabilities for each audio chunk,
    +                                        probabilities ABOVE this value are considered as SPEECH. It is better to tune
    +                                        this parameter for each dataset separately, but "lazy" 0.5 is pretty good for
    +                                        most datasets.
    +        :param sampling_rate:           Currently, silero VAD models support 8000 and 16000 sample rates.
    +        :param min_speech_duration_ms:  Final speech chunks shorter min_speech_duration_ms are thrown out.
    +        :param max_speech_duration_s:   Maximum duration of speech chunks in seconds. Chunks longer than
    +                                        `max_speech_duration_s` will be split at the timestamp of the last silence that
    +                                        lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise,
    +                                        they will be split aggressively just before max_speech_duration_s.
    +        :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before
    +                                        separating it.
    +        :param window_size_samples:     Audio chunks of window_size_samples size are fed to the silero VAD model.
    +                                        WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000
    +                                        sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than
    +                                        these may affect model performance!
    +        :param speech_pad_ms:           Final speech chunks are padded by speech_pad_ms each side.
    +        :param return_seconds:          Whether return timestamps in seconds. False means to return timestamps in
    +                                        samples (default - False).
    +        :param per_channel:             Whether to return timestamps per channel (default - False). This will run VAD
    +                                        on each channel separately and return a list of timestamps per channel.
    +        """
    +        # Store configurations:
    +        self._use_onnx = use_onnx
    +        self._force_onnx_cpu = force_onnx_cpu
    +        self._threshold = threshold
    +        self._sampling_rate = sampling_rate
    +        self._min_speech_duration_ms = min_speech_duration_ms
    +        self._max_speech_duration_s = max_speech_duration_s
    +        self._min_silence_duration_ms = min_silence_duration_ms
    +        self._window_size_samples = window_size_samples
    +        self._speech_pad_ms = speech_pad_ms
    +        self._return_seconds = return_seconds
    +        self._per_channel = per_channel
    +
    +        # Prepare the model variables
    +        self._model: torch.Module = None
    +        self._get_speech_timestamps: FunctionType = None
    +
    +    def load(self, force_reload: bool = True):
    +        """
    +        Load the VAD model.
    +
    +        :param force_reload: Whether to force reload the model even if it was already loaded. Default is True.
    +        """
    +        model, utils = torch.hub.load(
    +            repo_or_dir="snakers4/silero-vad",
    +            model="silero_vad",
    +            force_reload=force_reload,
    +            onnx=self._use_onnx,
    +            force_onnx_cpu=self._force_onnx_cpu,
    +        )
    +        self._model = model
    +        (
    +            self._get_speech_timestamps,
    +            _,  # save_audio,
    +            _,  # read_audio,
    +            _,  # VADIterator,
    +            _,  # collect_chunks
    +        ) = utils
    +
    +    def detect_voice(
    +        self,
    +        audio_file: Path,
    +    ) -> Union[List[Dict[str, int]], List[List[Dict[str, int]]]]:
    +        """
    +        Infer the audio through the VAD model and return the speech timestamps.
    +
    +        :param audio_file: The audio file to infer.
    +
    +        :returns: The speech timestamps in the audio. A list of timestamps where each timestamp is a dictionary with the
    +                 following keys:
    +
    +                 * "start": The start sample index of the speech in the audio.
    +                 * "end":   The end sample index of the speech in the audio.
    +
    +                 If `per_channel` is True, a list of timestamps per channel will be returned.
    +        """
    +        # Cast to a numpy array:
    +        audio = self._read_audio(audio_file)
    +
    +        # Detect speech:
    +        if not self._per_channel:
    +            return self._get_speech_timestamps(
    +                audio,
    +                self._model,
    +                threshold=self._threshold,
    +                min_speech_duration_ms=self._min_speech_duration_ms,
    +                max_speech_duration_s=self._max_speech_duration_s,
    +                min_silence_duration_ms=self._min_silence_duration_ms,
    +                speech_pad_ms=self._speech_pad_ms,
    +                sampling_rate=self._sampling_rate,
    +                window_size_samples=self._window_size_samples,
    +                return_seconds=self._return_seconds,
    +            )
    +
    +        # Per channel:
    +        speech_timestamps = []
    +        for channel in audio:
    +            speech_timestamps.append(
    +                self._get_speech_timestamps(
    +                    channel,
    +                    self._model,
    +                    threshold=self._threshold,
    +                    min_speech_duration_ms=self._min_speech_duration_ms,
    +                    max_speech_duration_s=self._max_speech_duration_s,
    +                    min_silence_duration_ms=self._min_silence_duration_ms,
    +                    speech_pad_ms=self._speech_pad_ms,
    +                    sampling_rate=self._sampling_rate,
    +                    window_size_samples=self._window_size_samples,
    +                    return_seconds=self._return_seconds,
    +                )
    +            )
    +
    +        return speech_timestamps
    +
    +    def _read_audio(
    +        self,
    +        path: Path,
    +    ) -> torch.Tensor:
    +        """
    +        Read the audio from the given path and return it as a tensor.
    +
    +        :param path: The path to the audio file.
    +
    +        :returns: The audio as a tensor.
    +        """
    +        # Read the audio:
    +        audio, sampling_rate = torchaudio.load(str(path))
    +
    +        # Check if the audio is stereo and if so, convert it to mono (only if not per channel):
    +        if audio.size(0) > 1 and not self._per_channel:
    +            audio = audio.mean(dim=0, keepdim=True)
    +
    +        # Resample the audio if needed:
    +        if sampling_rate != self._sampling_rate:
    +            transform = torchaudio.transforms.Resample(
    +                orig_freq=sampling_rate, new_freq=self._sampling_rate
    +            )
    +            audio = transform(audio)
    +
    +        # Return the audio (squeeze if not per channel):
    +        return audio if self._per_channel else audio.squeeze(0)
    +
    +
    +#: The value to send into multiprocessing queues to stop the process:
    +_MULTIPROCESSING_STOP_MARK = "STOP"
    +
    +
    +def _multiprocessing_complete_tasks(
    +    vad_init_kwargs: dict, tasks_queue: Queue, results_queue: Queue
    +):
    +    """
    +    Complete the tasks in the given queue and put the results in the given results queue. The function will stop when
    +    the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process.
    +
    +    :param vad_init_kwargs: The VAD initialization kwargs.
    +    :param tasks_queue:     A queue to get the tasks from.
    +    :param results_queue:   A queue to put the results in.
    +    """
    +    # Initialize and load the VAD:
    +    vad = VoiceActivityDetector(**vad_init_kwargs)
    +    vad.load(force_reload=False)
    +
    +    # Start listening to the tasks queue:
    +    while True:
    +        # Get the task:
    +        task: Tuple[str, dict] = tasks_queue.get()
    +        if task == _MULTIPROCESSING_STOP_MARK:
    +            break
    +        try:
    +            # Create the task:
    +            task = TaskCreator.from_tuple(task_tuple=task)
    +            # Run the file through the VAD:
    +            speech_timestamps = vad.detect_voice(audio_file=task.audio_file)
    +            # Complete the task:
    +            task.do_task(speech_timestamps=speech_timestamps)
    +            # Build the result:
    +            result = (False, task.get_result())
    +        except Exception as exception:
    +            # Build the error:
    +            result = (True, (task.audio_file.name, str(exception)))
    +        # Collect the result / error:
    +        results_queue.put(result)
    +
    +    # Mark the end of the tasks:
    +    results_queue.put(_MULTIPROCESSING_STOP_MARK)
    +
    +
    +# Get the global logger:
    +try:
    +    import mlrun
    +
    +    _LOGGER = mlrun.get_or_create_ctx("silero_vad").logger
    +except ModuleNotFoundError:
    +    _LOGGER = logging.getLogger()
    +
    +
    +def detect_voice(
    +    # Input kwargs:
    +    data_path: Union[str, Path, List[Union[str, Path]]],
    +    # Model loading kwargs:
    +    use_onnx: bool = True,
    +    force_onnx_cpu: bool = True,
    +    # Detection kwargs:
    +    threshold: float = 0.5,
    +    sampling_rate: int = 16_000,
    +    min_speech_duration_ms: int = 250,
    +    max_speech_duration_s: float = float("inf"),
    +    min_silence_duration_ms: int = 100,
    +    window_size_samples: int = 512,
    +    speech_pad_ms: int = 30,
    +    return_seconds: bool = False,
    +    per_channel: bool = False,
    +    # Other kwargs:
    +    use_multiprocessing: int = 0,
    +    verbose: bool = False,
    +):
    +    """
    +    Perform voice activity detection on given audio files using the silero VAD model -
    +    https://github.com/snakers4/silero-vad. The end result is a dictionary with the file names as keys and their
    +    VAD timestamps dictionaries as value.
    +
    +    For example::
    +
    +        {
    +            "file_1.wav": [
    +                {"start": 0, "end": 16000},
    +                {"start": 16000, "end": 32000},
    +                {"start": 32000, "end": 48000},
    +                ...
    +            ],
    +            "file_2.wav": [
    +                {"start": 0, "end": 16000},
    +                {"start": 16000, "end": 32000},
    +                {"start": 32000, "end": 48000},
    +                ...
    +            ],
    +            ...
    +        }
    +
    +
    +    :param data_path:               The path to the audio files to diarize. Can be a path to a single file, a path to a
    +                                    directory or a list of paths to files.
    +    :param use_onnx:                Whether to use ONNX for inference. Default is True.
    +    :param force_onnx_cpu:          Whether to force ONNX to use CPU for inference. Default is True.
    +    :param threshold:               Speech threshold. Silero VAD outputs speech probabilities for each audio chunk,
    +                                    probabilities ABOVE this value are considered as SPEECH. It is better to tune
    +                                    this parameter for each dataset separately, but "lazy" 0.5 is pretty good for
    +                                    most datasets.
    +    :param sampling_rate:           Currently, silero VAD models support 8000 and 16000 sample rates.
    +    :param min_speech_duration_ms:  Final speech chunks shorter min_speech_duration_ms are thrown out.
    +    :param max_speech_duration_s:   Maximum duration of speech chunks in seconds. Chunks longer than
    +                                    `max_speech_duration_s` will be split at the timestamp of the last silence that
    +                                    lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will
    +                                    be split aggressively just before max_speech_duration_s.
    +    :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating
    +                                    it.
    +    :param window_size_samples:     Audio chunks of window_size_samples size are fed to the silero VAD model.
    +
    +                                    WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000
    +                                    sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than
    +                                    these may affect model performance!
    +    :param speech_pad_ms:           Final speech chunks are padded by speech_pad_ms each side.
    +    :param return_seconds:          Whether return timestamps in seconds. False means to return timestamps in samples
    +                                    (default - False).
    +    :param per_channel:             Whether to return timestamps per channel (default - False). This will run VAD on
    +                                    each channel separately and return a list of timestamps per channel.
    +    :param use_multiprocessing:     The number of workers to use for multiprocessing. If 0, no multiprocessing will
    +                                    be used. Default is 0.
    +    :param verbose:                 Verbosity.
    +    """
    +    global _LOGGER
    +
    +    # Get the input audio files to transcribe:
    +    if verbose:
    +        _LOGGER.info("Collecting audio files.")
    +    audio_files = _get_audio_files(data_path=data_path)
    +    if verbose:
    +        _LOGGER.info(f"Collected {len(audio_files)} audio files.")
    +
    +    # Initialize the transcription pipeline:
    +    vad_init_kwargs = {
    +        "use_onnx": use_onnx,
    +        "force_onnx_cpu": force_onnx_cpu,
    +        "threshold": threshold,
    +        "sampling_rate": sampling_rate,
    +        "min_speech_duration_ms": min_speech_duration_ms,
    +        "max_speech_duration_s": max_speech_duration_s,
    +        "min_silence_duration_ms": min_silence_duration_ms,
    +        "window_size_samples": window_size_samples,
    +        "speech_pad_ms": speech_pad_ms,
    +        "return_seconds": return_seconds,
    +        "per_channel": per_channel,
    +    }
    +
    +    # Create the task creator:
    +    task_creator = TaskCreator(task_type=BaseTask)
    +
    +    # Run the transcription:
    +    if use_multiprocessing:
    +        results = _parallel_run(
    +            n_workers=use_multiprocessing,
    +            audio_files=audio_files,
    +            description="Detecting voice",
    +            vad_init_kwargs=vad_init_kwargs,
    +            task_creator=task_creator,
    +            verbose=verbose,
    +        )
    +    else:
    +        results = _run(
    +            audio_files=audio_files,
    +            description="Detecting voice",
    +            vad_init_kwargs=vad_init_kwargs,
    +            task_creator=task_creator,
    +            verbose=verbose,
    +        )
    +
    +    # Process the results:
    +    return _process_results(results=results, verbose=verbose)
    +
    +
    +def diarize(
    +    # Input / Output kwargs:
    +    data_path: Union[str, Path, List[Union[str, Path]]],
    +    # Model loading kwargs:
    +    use_onnx: bool = True,
    +    force_onnx_cpu: bool = True,
    +    # Detection kwargs:
    +    threshold: float = 0.5,
    +    sampling_rate: int = 16_000,
    +    min_speech_duration_ms: int = 250,
    +    max_speech_duration_s: float = float("inf"),
    +    min_silence_duration_ms: int = 100,
    +    window_size_samples: int = 512,
    +    speech_pad_ms: int = 30,
    +    # Diarization kwargs:
    +    speaker_labels: List[str] = None,
    +    # Other kwargs:
    +    use_multiprocessing: int = 0,
    +    verbose: bool = False,
    +):
    +    """
    +    Perform speech diarization on given audio files using the silero VAD model - https://github.com/snakers4/silero-vad.
    +    The speech diarization is performed per channel so that each channel in the audio belong to a different speaker. The
    +    end result is a dictionary with the file names as keys and their diarization as value. A diarization is a list
    +    of tuples: (start, end, speaker_label).
    +
    +    For example::
    +
    +        {
    +            "file_1.wav": [
    +                (0.0, 1.0, "speaker_0"),
    +                (1.0, 2.0, "speaker_1"),
    +                (2.0, 3.0, "speaker_0"),
    +                ...
    +            ],
    +            "file_2.wav": [
    +                (0.0, 1.0, "speaker_0"),
    +                (1.0, 2.0, "speaker_1"),
    +                (2.0, 3.0, "speaker_0"),
    +                ...
    +            ],
    +            ...
    +        }
    +
    +
    +    :param data_path:               The path to the audio files to diarize. Can be a path to a single file, a path to a
    +                                    directory or a list of paths to files.
    +    :param use_onnx:                Whether to use ONNX for inference. Default is True.
    +    :param force_onnx_cpu:          Whether to force ONNX to use CPU for inference. Default is True.
    +    :param threshold:               Speech threshold. Silero VAD outputs speech probabilities for each audio chunk,
    +                                    probabilities ABOVE this value are considered as SPEECH. It is better to tune
    +                                    this parameter for each dataset separately, but "lazy" 0.5 is pretty good for
    +                                    most datasets.
    +    :param sampling_rate:           Currently, silero VAD models support 8000 and 16000 sample rates.
    +    :param min_speech_duration_ms:  Final speech chunks shorter min_speech_duration_ms are thrown out.
    +    :param max_speech_duration_s:   Maximum duration of speech chunks in seconds. Chunks longer than
    +                                    `max_speech_duration_s` will be split at the timestamp of the last silence that
    +                                    lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will
    +                                    be split aggressively just before max_speech_duration_s.
    +    :param min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms before separating
    +                                    it.
    +    :param window_size_samples:     Audio chunks of window_size_samples size are fed to the silero VAD model.
    +
    +                                    WARNING! Silero VAD models were trained using 512, 1024, 1536 samples for 16000
    +                                    sample rate and 256, 512, 768 samples for 8000 sample rate. Values other than
    +                                    these may affect model performance!
    +    :param speech_pad_ms:           Final speech chunks are padded by speech_pad_ms each side.
    +    :param speaker_labels:          The speaker labels to use for the diarization. If not given, the speakers will be
    +                                    named "speaker_0", "speaker_1", etc.
    +    :param use_multiprocessing:     The number of workers to use for multiprocessing. If 0, no multiprocessing will
    +                                    be used. Default is 0.
    +    :param verbose:                 Verbosity.
    +    """
    +    global _LOGGER
    +
    +    # Get the input audio files to transcribe:
    +    if verbose:
    +        _LOGGER.info("Collecting audio files.")
    +    audio_files = _get_audio_files(data_path=data_path)
    +    if verbose:
    +        _LOGGER.info(f"Collected {len(audio_files)} audio files.")
    +
    +    # Initialize the transcription pipeline:
    +    vad_init_kwargs = {
    +        "use_onnx": use_onnx,
    +        "force_onnx_cpu": force_onnx_cpu,
    +        "threshold": threshold,
    +        "sampling_rate": sampling_rate,
    +        "min_speech_duration_ms": min_speech_duration_ms,
    +        "max_speech_duration_s": max_speech_duration_s,
    +        "min_silence_duration_ms": min_silence_duration_ms,
    +        "window_size_samples": window_size_samples,
    +        "speech_pad_ms": speech_pad_ms,
    +        "return_seconds": True,
    +        "per_channel": True,
    +    }
    +
    +    # Create the task creator:
    +    task_creator = TaskCreator(
    +        task_type=SpeechDiarizationTask, task_kwargs={"speaker_labels": speaker_labels}
    +    )
    +
    +    # Run the transcription:
    +    if use_multiprocessing:
    +        results = _parallel_run(
    +            n_workers=use_multiprocessing,
    +            audio_files=audio_files,
    +            description="Diarizing",
    +            vad_init_kwargs=vad_init_kwargs,
    +            task_creator=task_creator,
    +            verbose=verbose,
    +        )
    +    else:
    +        results = _run(
    +            audio_files=audio_files,
    +            description="Diarizing",
    +            vad_init_kwargs=vad_init_kwargs,
    +            task_creator=task_creator,
    +            verbose=verbose,
    +        )
    +
    +    # Process the results:
    +    return _process_results(results=results, verbose=verbose)
    +
    +
    +def _get_audio_files(
    +    data_path: Union[Path, str, list],
    +) -> List[Path]:
    +    """
    +    Get the audio files from the data path. If a path to a directory is given, all files in the directory will be
    +    collected.
    +
    +    :param data_path: The data path to collect the audio files from.
    +
    +    :returns: The audio files list.
    +    """
    +    # Check if given a list of paths:
    +    if isinstance(data_path, list):
    +        audio_files = []
    +        for path in data_path:
    +            audio_files.extend(_get_audio_files(data_path=path))
    +        return audio_files
    +
    +    # Check if given a single string path to cast it to a `pathlib.Path`:
    +    if isinstance(data_path, str):
    +        data_path = Path(data_path).absolute()
    +
    +    # Check if the path is of a directory or a file:
    +    if data_path.is_dir():
    +        # Get all files inside the directory:
    +        audio_files = list(data_path.glob("*.*"))
    +    elif data_path.is_file():
    +        audio_files = [data_path]
    +    else:
    +        raise ValueError(
    +            f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a "
    +            f"file. Given: {str(data_path)} "
    +        )
    +
    +    return audio_files
    +
    +
    +def _run(
    +    audio_files: List[Path],
    +    description: str,
    +    vad_init_kwargs: dict,
    +    task_creator: TaskCreator,
    +    verbose: bool,
    +) -> List[Tuple[bool, Tuple[str, list]]]:
    +    """
    +    Load a VAD and use it to complete the tasks that will be created on the provided files using the given task creator.
    +
    +    :param audio_files:     The audio files to use.
    +    :param description:     The description to use for the progress bar.
    +    :param vad_init_kwargs: The VAD initialization keyword arguments.
    +    :param task_creator:    The task creator to use to create the tasks.
    +    :param verbose:         Verbosity.
    +
    +    :returns: The collected results.
    +    """
    +    # Load the VAD:
    +    vad = VoiceActivityDetector(**vad_init_kwargs)
    +    if verbose:
    +        _LOGGER.info(f"Loading the VAD model.")
    +    vad.load()
    +    if verbose:
    +        _LOGGER.info("VAD model loaded.")
    +
    +    # Run the VAD on the audio files and collect the results:
    +    results = []
    +    for audio_file in tqdm(
    +        audio_files,
    +        desc=description,
    +        unit="file",
    +        total=len(audio_files),
    +        disable=not verbose,
    +    ):
    +        try:
    +            # Create the task:
    +            task = task_creator.create_task(audio_file=audio_file)
    +            # Run the file through the VAD:
    +            speech_timestamps = vad.detect_voice(audio_file=audio_file)
    +            # Complete the task:
    +            task.do_task(speech_timestamps=speech_timestamps)
    +            # Collect the result:
    +            results.append((False, task.get_result()))
    +        except Exception as exception:
    +            # Collect the error:
    +            results.append((True, (audio_file.name, str(exception))))
    +
    +    return results
    +
    +
    +def _parallel_run(
    +    n_workers: int,
    +    audio_files: List[Path],
    +    description: str,
    +    vad_init_kwargs: dict,
    +    task_creator: TaskCreator,
    +    verbose: bool,
    +) -> List[Tuple[bool, Tuple[str, list]]]:
    +    """
    +    Run multiple VAD workers with multiprocessing to complete the tasks that will be created on the provided files using
    +    the given task creator.
    +
    +    :param n_workers:       The number of workers to use.
    +    :param audio_files:     The audio files to use.
    +    :param description:     The description to use for the progress bar.
    +    :param vad_init_kwargs: The VAD initialization keyword arguments.
    +    :param task_creator:    The task creator to use to create the tasks.
    +    :param verbose:         Verbosity.
    +
    +    :returns: The collected results.
    +    """
    +    # Load the VAD (download once, and it will be loaded then per process later on):
    +    if verbose:
    +        _LOGGER.info(f"Loading the VAD model.")
    +    vad = VoiceActivityDetector(**vad_init_kwargs)
    +    vad.load()
    +    if verbose:
    +        _LOGGER.info("VAD model loaded.")
    +
    +    # Check the number of workers:
    +    if n_workers > len(audio_files):
    +        _LOGGER.warning(
    +            f"The number of workers ({n_workers}) is larger than the number of audio files ({len(audio_files)}). "
    +            f"Setting the number of workers to {len(audio_files)}."
    +        )
    +        n_workers = len(audio_files)
    +
    +    # Initialize the multiprocessing queues:
    +    tasks_queue = Queue()
    +    results_queue = Queue()
    +
    +    # Initialize the multiprocessing processes:
    +    task_completion_processes = [
    +        Process(
    +            target=_multiprocessing_complete_tasks,
    +            kwargs={
    +                "vad_init_kwargs": vad_init_kwargs,
    +                "tasks_queue": tasks_queue,
    +                "results_queue": results_queue,
    +            },
    +        )
    +        for _ in range(n_workers)
    +    ]
    +
    +    # Start the multiprocessing processes:
    +    for p in task_completion_processes:
    +        p.start()
    +
    +    # Put the tasks in the queue:
    +    for audio_file in audio_files:
    +        tasks_queue.put(task_creator.create_task(audio_file=audio_file).to_tuple())
    +
    +    # Put the stop marks in the queue:
    +    for _ in range(n_workers):
    +        tasks_queue.put(_MULTIPROCESSING_STOP_MARK)
    +
    +    # Collect the results:
    +    results = []
    +    stop_marks_counter = 0
    +    with tqdm(
    +        desc=description,
    +        unit="file",
    +        total=len(audio_files),
    +        disable=not verbose,
    +    ) as progressbar:
    +        while True:
    +            # Get a result from the queue:
    +            result: Tuple[bool, Tuple[str, list]] = results_queue.get()
    +            if result == _MULTIPROCESSING_STOP_MARK:
    +                stop_marks_counter += 1
    +                if stop_marks_counter == n_workers:
    +                    break
    +            else:
    +                # Collect the result:
    +                results.append(result)
    +                progressbar.update(1)
    +
    +    # Wait for the processes to finish:
    +    for p in task_completion_processes:
    +        p.join()
    +
    +    return results
    +
    +
    +def _process_results(
    +    results: List[Tuple[bool, Tuple[str, list]]], verbose: bool
    +) -> Tuple[dict, dict]:
    +    """
    +    Process the results of the tasks.
    +
    +    :param results: The results to process.
    +    :param verbose: Verbosity.
    +
    +    :returns: The processed results as a tuple of successes and errors.
    +    """
    +    if verbose:
    +        _LOGGER.info("Summarizing the results.")
    +    successes = {}
    +    errors = {}
    +    for is_error, result in results:
    +        if is_error:
    +            errors[result[0]] = result[1]
    +        else:
    +            successes[result[0]] = result[1]
    +    if verbose:
    +        _LOGGER.info(f"Done ({len(successes)}/{len(successes) + len(errors)})\n")
    +
    +    return successes, errors
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/silero_vad/latest/src/function.yaml b/functions/master/silero_vad/latest/src/function.yaml index 8ec121a6..fd637f1c 100644 --- a/functions/master/silero_vad/latest/src/function.yaml +++ b/functions/master/silero_vad/latest/src/function.yaml @@ -1,110 +1,104 @@ -kind: job metadata: - name: silero-vad tag: '' - hash: 59336f808643a74f3a2c5d506977387010427208 - project: '' - labels: - author: guyl categories: - deep-learning - - pytorch - audio + name: silero-vad +verbose: false spec: - command: '' - args: [] - image: '' + description: Silero VAD (Voice Activity Detection) functions. build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK - base_image: mlrun/mlrun - commands: [] code_origin: '' - origin_filename: '' + base_image: mlrun/mlrun requirements: - torch - torchaudio - tqdm - onnxruntime + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK + origin_filename: '' + image: '' + command: '' entry_points: audio_file: - name: audio_file doc: Get the audio file of the task. - parameters: - - name: self + lineno: 43 + has_varargs: false outputs: - doc: The audio file of the task. type: Path - lineno: 43 - has_varargs: false + parameters: + - name: self has_kwargs: false + name: audio_file do_task: - name: do_task doc: Do the task on the given speech timestamps. The task will diarize the VAD speech timestamps into speakers. + lineno: 94 + has_varargs: false parameters: - name: self - name: speech_timestamps type: List[List[Dict[str, int]]] doc: The speech timestamps per channel to do the task on as outputted from the VAD. - outputs: [] - lineno: 94 - has_varargs: false has_kwargs: false + name: do_task get_result: - name: get_result doc: Get the result of the task. A tuple of the audio file name and the result. - parameters: - - name: self + lineno: 61 + has_varargs: false outputs: - doc: The result of the task. type: Tuple[str, list] - lineno: 61 - has_varargs: false + parameters: + - name: self has_kwargs: false + name: get_result to_tuple: - name: to_tuple doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). - parameters: - - name: self + lineno: 116 + has_varargs: false outputs: - doc: The converted task. type: Tuple[str, dict] - lineno: 116 - has_varargs: false + parameters: + - name: self has_kwargs: false + name: to_tuple create_task: - name: create_task doc: Create a task with the given audio file. + lineno: 146 + has_varargs: false + outputs: + - doc: The created task. + type: BaseTask parameters: - name: self - name: audio_file type: Path doc: The audio file to assign to the task. - outputs: - - doc: The created task. - type: BaseTask - lineno: 146 - has_varargs: false has_kwargs: false + name: create_task from_tuple: - name: from_tuple doc: Create a task from a tuple of the audio file name and the task kwargs. + lineno: 157 + has_varargs: false + outputs: + - doc: The created task. + type: BaseTask parameters: - name: cls - name: task_tuple type: Tuple[str, dict] doc: The task tuple to create the task from. - outputs: - - doc: The created task. - type: BaseTask - lineno: 157 - has_varargs: false has_kwargs: false + name: from_tuple load: - name: load doc: Load the VAD model. + lineno: 234 + has_varargs: false parameters: - name: self - name: force_reload @@ -112,12 +106,9 @@ spec: doc: Whether to force reload the model even if it was already loaded. Default is True. default: true - outputs: [] - lineno: 234 - has_varargs: false has_kwargs: false + name: load detect_voice: - name: detect_voice doc: "Perform voice activity detection on given audio files using the silero\ \ VAD model -\nhttps://github.com/snakers4/silero-vad. The end result is a\ \ dictionary with the file names as keys and their\nVAD timestamps dictionaries\ @@ -128,6 +119,8 @@ spec: : 16000},\n {\"start\": 16000, \"end\": 32000},\n {\"\ start\": 32000, \"end\": 48000},\n ...\n ],\n ...\n\ \ }" + lineno: 393 + has_varargs: false parameters: - name: data_path type: Union[str, Path, List[Union[str, Path]]] @@ -195,12 +188,9 @@ spec: type: bool doc: Verbosity. default: false - outputs: [] - lineno: 393 - has_varargs: false has_kwargs: false + name: detect_voice diarize: - name: diarize doc: "Perform speech diarization on given audio files using the silero VAD model\ \ - https://github.com/snakers4/silero-vad.\nThe speech diarization is performed\ \ per channel so that each channel in the audio belong to a different speaker.\ @@ -212,6 +202,8 @@ spec: : [\n (0.0, 1.0, \"speaker_0\"),\n (1.0, 2.0, \"speaker_1\"\ ),\n (2.0, 3.0, \"speaker_0\"),\n ...\n ],\n\ \ ...\n }" + lineno: 517 + has_varargs: false parameters: - name: data_path type: Union[str, Path, List[Union[str, Path]]] @@ -274,18 +266,8 @@ spec: type: bool doc: Verbosity. default: false - outputs: [] - lineno: 517 - has_varargs: false has_kwargs: false - description: Silero VAD (Voice Activity Detection) functions. - default_handler: detect_voice + name: diarize disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false + default_handler: detect_voice +kind: job diff --git a/functions/master/silero_vad/latest/src/item.yaml b/functions/master/silero_vad/latest/src/item.yaml index 9ce9a5d2..49adfcd9 100644 --- a/functions/master/silero_vad/latest/src/item.yaml +++ b/functions/master/silero_vad/latest/src/item.yaml @@ -1,7 +1,6 @@ apiVersion: v1 categories: - deep-learning -- pytorch - audio description: Silero VAD (Voice Activity Detection) functions. doc: '' @@ -13,7 +12,7 @@ labels: author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: silero_vad platformVersion: 3.5.3 spec: @@ -27,4 +26,4 @@ spec: - tqdm - onnxruntime url: '' -version: 1.3.0 +version: 1.4.0 diff --git a/functions/master/silero_vad/latest/static/documentation.html b/functions/master/silero_vad/latest/static/documentation.html index a24baa8e..344c2421 100644 --- a/functions/master/silero_vad/latest/static/documentation.html +++ b/functions/master/silero_vad/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/silero_vad/latest/static/example.html b/functions/master/silero_vad/latest/static/example.html index bb7366a9..0eb7542f 100644 --- a/functions/master/silero_vad/latest/static/example.html +++ b/functions/master/silero_vad/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/silero_vad/latest/static/function.html b/functions/master/silero_vad/latest/static/function.html index 051e0cf1..839ab544 100644 --- a/functions/master/silero_vad/latest/static/function.html +++ b/functions/master/silero_vad/latest/static/function.html @@ -28,113 +28,107 @@
             
    -kind: job
     metadata:
    -  name: silero-vad
       tag: ''
    -  hash: 59336f808643a74f3a2c5d506977387010427208
    -  project: ''
    -  labels:
    -    author: guyl
       categories:
       - deep-learning
    -  - pytorch
       - audio
    +  name: silero-vad
    +verbose: false
     spec:
    -  command: ''
    -  args: []
    -  image: ''
    +  description: Silero VAD (Voice Activity Detection) functions.
       build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
    -    base_image: mlrun/mlrun
    -    commands: []
         code_origin: ''
    -    origin_filename: ''
    +    base_image: mlrun/mlrun
         requirements:
         - torch
         - torchaudio
         - tqdm
         - onnxruntime
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwZXMgaW1wb3J0IEZ1bmN0aW9uVHlwZQpmcm9tIHR5cGluZyBpbXBvcnQgRGljdCwgTGlzdCwgVHVwbGUsIFR5cGUsIFVuaW9uCgppbXBvcnQgdG9yY2gKaW1wb3J0IHRvcmNoYXVkaW8KZnJvbSB0cWRtIGltcG9ydCB0cWRtCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgYmFzZSBjbGFzcyBmb3IgYSB0YXNrIHRvIGNvbXBsZXRlIGFmdGVyIFZBRC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBhdWRpb19maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXNlIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiBUaGUgYXVkaW8gZmlsZSBhc3NpZ25lZCB0byB0aGUgdGFzay4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIHRoZSBhdWRpbyBmaWxlOgogICAgICAgIHNlbGYuX2F1ZGlvX2ZpbGUgPSBhdWRpb19maWxlCgogICAgICAgICMgUHJlcGFyZSB0aGUgcmVzdWx0OgogICAgICAgIHNlbGYuX3Jlc3VsdCA9IE5vbmUKCiAgICBAcHJvcGVydHkKICAgIGRlZiBhdWRpb19maWxlKHNlbGYpIC0+IFBhdGg6CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSBhdWRpbyBmaWxlIG9mIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGUgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUKCiAgICBkZWYgZG9fdGFzaygKICAgICAgICBzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXQogICAgKToKICAgICAgICAiIiIKICAgICAgICBEbyB0aGUgdGFzayBvbiB0aGUgZ2l2ZW4gc3BlZWNoIHRpbWVzdGFtcHMuIFRoZSBiYXNlIHRhc2sgd2lsbCBzaW1wbHkgc2F2ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgYXMgdGhlIHJlc3VsdC4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICBzZWxmLl9yZXN1bHQgPSBzcGVlY2hfdGltZXN0YW1wcwoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgbGlzdF06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSByZXN1bHQgb2YgdGhlIHRhc2suIEEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHJlc3VsdC4KCiAgICAgICAgOnJldHVybnM6IFRoZSByZXN1bHQgb2YgdGhlIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX2F1ZGlvX2ZpbGUubmFtZSwgc2VsZi5fcmVzdWx0CgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7ImF1ZGlvX2ZpbGUiOiBzZWxmLl9hdWRpb19maWxlfQoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uVGFzayhCYXNlVGFzayk6CiAgICAiIiIKICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suIFRoZSB0YXNrIHdpbGwgZGlhcml6ZSB0aGUgVkFEIHNwZWVjaCB0aW1lc3RhbXBzIGludG8gc3BlYWtlcnMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgc3BlYWtlcl9sYWJlbHM6IExpc3Rbc3RyXSk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgc3BlZWNoIGRpYXJpemF0aW9uIHRhc2suCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlOiAgICAgVGhlIGF1ZGlvIGZpbGUgYXNzaWduZWQgdG8gdGhlIHRhc2suCiAgICAgICAgOnBhcmFtIHNwZWFrZXJfbGFiZWxzOiBUaGUgc3BlYWtlciBsYWJlbHMgdG8gdXNlIGZvciB0aGUgZGlhcml6YXRpb24uIElmIG5vdCBnaXZlbiwgdGhlIHNwZWFrZXJzIHdpbGwgYmUgbmFtZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgIHNlbGYuX3NwZWFrZXJfbGFiZWxzID0gc3BlYWtlcl9sYWJlbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmLCBzcGVlY2hfdGltZXN0YW1wczogTGlzdFtMaXN0W0RpY3Rbc3RyLCBpbnRdXV0pOgogICAgICAgICIiIgogICAgICAgIERvIHRoZSB0YXNrIG9uIHRoZSBnaXZlbiBzcGVlY2ggdGltZXN0YW1wcy4gVGhlIHRhc2sgd2lsbCBkaWFyaXplIHRoZSBWQUQgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBzcGVha2Vycy4KCiAgICAgICAgOnBhcmFtIHNwZWVjaF90aW1lc3RhbXBzOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgdG8gZG8gdGhlIHRhc2sgb24gYXMgb3V0cHV0dGVkIGZyb20gdGhlIFZBRC4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgc3BlYWtlciBsYWJlbHMgKHNldCBkZWZhdWx0IGlmIG5vdCBnaXZlbik6CiAgICAgICAgc3BlYWtlcl9sYWJlbHMgPSBzZWxmLl9zcGVha2VyX2xhYmVscyBvciBbCiAgICAgICAgICAgIGYic3BlYWtlcl97aX0iIGZvciBpIGluIHJhbmdlKGxlbihzcGVlY2hfdGltZXN0YW1wcykpCiAgICAgICAgXQoKICAgICAgICAjIERpYXJpemUgLSBvcmdhbml6ZSB0aGUgc3BlZWNoIHRpbWVzdGFtcHMgaW50byBhIHNpbmdsZSBsaXN0IG9mIHNwZWFrZXJzIGFuZCBzb3J0IGl0IGJ5IHN0YXJ0IHRpbWU6CiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uID0gWwogICAgICAgICAgICAoc3BlZWNoX3RpbWVzdGFtcFsic3RhcnQiXSwgc3BlZWNoX3RpbWVzdGFtcFsiZW5kIl0sIHNwZWFrZXJfbGFiZWwpCiAgICAgICAgICAgIGZvciBzcGVha2VyX2xhYmVsLCBjaGFubmVsX3NwZWVjaF90aW1lc3RhbXBzIGluIHppcCgKICAgICAgICAgICAgICAgIHNwZWFrZXJfbGFiZWxzLCBzcGVlY2hfdGltZXN0YW1wcwogICAgICAgICAgICApCiAgICAgICAgICAgIGZvciBzcGVlY2hfdGltZXN0YW1wIGluIGNoYW5uZWxfc3BlZWNoX3RpbWVzdGFtcHMKICAgICAgICBdCiAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uLnNvcnQoKQogICAgICAgIHNlbGYuX3Jlc3VsdCA9IHNwZWVjaF9kaWFyaXphdGlvbgoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsqKnRhc2tfa3dhcmdzLCAic3BlYWtlcl9sYWJlbHMiOiBzZWxmLl9zcGVha2VyX2xhYmVsc30KCgpjbGFzcyBUYXNrQ3JlYXRvcjoKICAgICIiIgogICAgQSB0YXNrIGNyZWF0b3IgdG8gY3JlYXRlIGRpZmZlcmVudCB0YXNrcyB0byBydW4gYWZ0ZXIgdGhlIFZBRC4KICAgICIiIgoKICAgICM6IEEgbWFwIGZyb20gdGFzayBjbGFzcyBuYW1lIHRvIHRhc2sgY2xhc3MgdG8gdXNlIGluIGBmcm9tX3R1cGxlYDoKICAgIF9NQVAgPSB7CiAgICAgICAgQmFzZVRhc2suX19uYW1lX186IEJhc2VUYXNrLAogICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25UYXNrLAogICAgfQoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB0YXNrX3R5cGU6IFR5cGVbQmFzZVRhc2tdLCB0YXNrX2t3YXJnczogZGljdCA9IE5vbmUpOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIHRhc2sgY3JlYXRvci4KICAgICAgICA6cGFyYW0gdGFza190eXBlOiBUaGUgdGFzayB0eXBlIC0gYSBgQmFzZVRhc2tgIHN1YmNsYXNzLgogICAgICAgIDpwYXJhbSB0YXNrX2t3YXJnczogQWRkaXRpb25hbCBrZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHRvIHRoZSB0byBiZSBjcmVhdGVkIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHNlbGYuX3Rhc2tfdHlwZSA9IHRhc2tfdHlwZQogICAgICAgIHNlbGYuX3Rhc2tfa3dhcmdzID0gdGFza19rd2FyZ3Mgb3Ige30KCiAgICBkZWYgY3JlYXRlX3Rhc2soc2VsZiwgYXVkaW9fZmlsZTogUGF0aCkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayB3aXRoIHRoZSBnaXZlbiBhdWRpbyBmaWxlLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gYXNzaWduIHRvIHRoZSB0YXNrLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNyZWF0ZWQgdGFzay4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdGFza190eXBlKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgKipzZWxmLl90YXNrX2t3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBmcm9tX3R1cGxlKGNscywgdGFza190dXBsZTogVHVwbGVbc3RyLCBkaWN0XSkgLT4gQmFzZVRhc2s6CiAgICAgICAgIiIiCiAgICAgICAgQ3JlYXRlIGEgdGFzayBmcm9tIGEgdHVwbGUgb2YgdGhlIGF1ZGlvIGZpbGUgbmFtZSBhbmQgdGhlIHRhc2sga3dhcmdzLgoKICAgICAgICA6cGFyYW0gdGFza190dXBsZTogVGhlIHRhc2sgdHVwbGUgdG8gY3JlYXRlIHRoZSB0YXNrIGZyb20uCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3JlYXRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gdGFza190dXBsZQogICAgICAgIHJldHVybiBjbHMuX01BUFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKCmNsYXNzIFZvaWNlQWN0aXZpdHlEZXRlY3RvcjoKICAgICIiIgogICAgQSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rpb24gd3JhcHBlciBmb3IgdGhlIHNpbGVybyBWQUQgbW9kZWwgLSBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICAgICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgICAgIGZvcmNlX29ubnhfY3B1OiBib29sID0gVHJ1ZSwKICAgICAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICAgICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgICAgICBzYW1wbGluZ19yYXRlOiBpbnQgPSAxNl8wMDAsCiAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM6IGludCA9IDEwMCwKICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICAgICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAgICAgcmV0dXJuX3NlY29uZHM6IGJvb2wgPSBGYWxzZSwKICAgICAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB2b2ljZSBhY3Rpdml0eSBkZXRlY3Rvci4KCiAgICAgICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gZm9yY2Vfb25ueF9jcHU6ICAgICAgICAgIFdoZXRoZXIgdG8gZm9yY2UgT05OWCB0byB1c2UgQ1BVIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgICAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzIHBhcmFtZXRlciBmb3IgZWFjaCBkYXRhc2V0IHNlcGFyYXRlbHksIGJ1dCAibGF6eSIgMC41IGlzIHByZXR0eSBnb29kIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgICAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICAgICAgOnBhcmFtIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6ICBGaW5hbCBzcGVlY2ggY2h1bmtzIHNob3J0ZXIgbWluX3NwZWVjaF9kdXJhdGlvbl9tcyBhcmUgdGhyb3duIG91dC4KICAgICAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RzIG1vcmUgdGhhbiAxMDBtcyAoaWYgYW55KSwgdG8gcHJldmVudCBhZ2dyZXNzaXZlIGN1dHRpbmcuIE90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXkgd2lsbCBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcGFyYXRpbmcgaXQuCiAgICAgICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlc2UgbWF5IGFmZmVjdCBtb2RlbCBwZXJmb3JtYW5jZSEKICAgICAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgICAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgKGRlZmF1bHQgLSBGYWxzZSkuCiAgICAgICAgOnBhcmFtIHBlcl9jaGFubmVsOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHJldHVybiB0aW1lc3RhbXBzIHBlciBjaGFubmVsIChkZWZhdWx0IC0gRmFsc2UpLiBUaGlzIHdpbGwgcnVuIFZBRAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb24gZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGNvbmZpZ3VyYXRpb25zOgogICAgICAgIHNlbGYuX3VzZV9vbm54ID0gdXNlX29ubngKICAgICAgICBzZWxmLl9mb3JjZV9vbm54X2NwdSA9IGZvcmNlX29ubnhfY3B1CiAgICAgICAgc2VsZi5fdGhyZXNob2xkID0gdGhyZXNob2xkCiAgICAgICAgc2VsZi5fc2FtcGxpbmdfcmF0ZSA9IHNhbXBsaW5nX3JhdGUKICAgICAgICBzZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zID0gbWluX3NwZWVjaF9kdXJhdGlvbl9tcwogICAgICAgIHNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcyA9IG1heF9zcGVlY2hfZHVyYXRpb25fcwogICAgICAgIHNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zID0gbWluX3NpbGVuY2VfZHVyYXRpb25fbXMKICAgICAgICBzZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzID0gd2luZG93X3NpemVfc2FtcGxlcwogICAgICAgIHNlbGYuX3NwZWVjaF9wYWRfbXMgPSBzcGVlY2hfcGFkX21zCiAgICAgICAgc2VsZi5fcmV0dXJuX3NlY29uZHMgPSByZXR1cm5fc2Vjb25kcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsID0gcGVyX2NoYW5uZWwKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBtb2RlbCB2YXJpYWJsZXMKICAgICAgICBzZWxmLl9tb2RlbDogdG9yY2guTW9kdWxlID0gTm9uZQogICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wczogRnVuY3Rpb25UeXBlID0gTm9uZQoKICAgIGRlZiBsb2FkKHNlbGYsIGZvcmNlX3JlbG9hZDogYm9vbCA9IFRydWUpOgogICAgICAgICIiIgogICAgICAgIExvYWQgdGhlIFZBRCBtb2RlbC4KCiAgICAgICAgOnBhcmFtIGZvcmNlX3JlbG9hZDogV2hldGhlciB0byBmb3JjZSByZWxvYWQgdGhlIG1vZGVsIGV2ZW4gaWYgaXQgd2FzIGFscmVhZHkgbG9hZGVkLiBEZWZhdWx0IGlzIFRydWUuCiAgICAgICAgIiIiCiAgICAgICAgbW9kZWwsIHV0aWxzID0gdG9yY2guaHViLmxvYWQoCiAgICAgICAgICAgIHJlcG9fb3JfZGlyPSJzbmFrZXJzNC9zaWxlcm8tdmFkIiwKICAgICAgICAgICAgbW9kZWw9InNpbGVyb192YWQiLAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9Zm9yY2VfcmVsb2FkLAogICAgICAgICAgICBvbm54PXNlbGYuX3VzZV9vbm54LAogICAgICAgICAgICBmb3JjZV9vbm54X2NwdT1zZWxmLl9mb3JjZV9vbm54X2NwdSwKICAgICAgICApCiAgICAgICAgc2VsZi5fbW9kZWwgPSBtb2RlbAogICAgICAgICgKICAgICAgICAgICAgc2VsZi5fZ2V0X3NwZWVjaF90aW1lc3RhbXBzLAogICAgICAgICAgICBfLCAgIyBzYXZlX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyByZWFkX2F1ZGlvLAogICAgICAgICAgICBfLCAgIyBWQURJdGVyYXRvciwKICAgICAgICAgICAgXywgICMgY29sbGVjdF9jaHVua3MKICAgICAgICApID0gdXRpbHMKCiAgICBkZWYgZGV0ZWN0X3ZvaWNlKAogICAgICAgIHNlbGYsCiAgICAgICAgYXVkaW9fZmlsZTogUGF0aCwKICAgICkgLT4gVW5pb25bTGlzdFtEaWN0W3N0ciwgaW50XV0sIExpc3RbTGlzdFtEaWN0W3N0ciwgaW50XV1dXToKICAgICAgICAiIiIKICAgICAgICBJbmZlciB0aGUgYXVkaW8gdGhyb3VnaCB0aGUgVkFEIG1vZGVsIGFuZCByZXR1cm4gdGhlIHNwZWVjaCB0aW1lc3RhbXBzLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogVGhlIGF1ZGlvIGZpbGUgdG8gaW5mZXIuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgc3BlZWNoIHRpbWVzdGFtcHMgaW4gdGhlIGF1ZGlvLiBBIGxpc3Qgb2YgdGltZXN0YW1wcyB3aGVyZSBlYWNoIHRpbWVzdGFtcCBpcyBhIGRpY3Rpb25hcnkgd2l0aCB0aGUKICAgICAgICAgICAgICAgICBmb2xsb3dpbmcga2V5czoKCiAgICAgICAgICAgICAgICAgKiAic3RhcnQiOiBUaGUgc3RhcnQgc2FtcGxlIGluZGV4IG9mIHRoZSBzcGVlY2ggaW4gdGhlIGF1ZGlvLgogICAgICAgICAgICAgICAgICogImVuZCI6ICAgVGhlIGVuZCBzYW1wbGUgaW5kZXggb2YgdGhlIHNwZWVjaCBpbiB0aGUgYXVkaW8uCgogICAgICAgICAgICAgICAgIElmIGBwZXJfY2hhbm5lbGAgaXMgVHJ1ZSwgYSBsaXN0IG9mIHRpbWVzdGFtcHMgcGVyIGNoYW5uZWwgd2lsbCBiZSByZXR1cm5lZC4KICAgICAgICAiIiIKICAgICAgICAjIENhc3QgdG8gYSBudW1weSBhcnJheToKICAgICAgICBhdWRpbyA9IHNlbGYuX3JlYWRfYXVkaW8oYXVkaW9fZmlsZSkKCiAgICAgICAgIyBEZXRlY3Qgc3BlZWNoOgogICAgICAgIGlmIG5vdCBzZWxmLl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgcmV0dXJuIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgIGF1ZGlvLAogICAgICAgICAgICAgICAgc2VsZi5fbW9kZWwsCiAgICAgICAgICAgICAgICB0aHJlc2hvbGQ9c2VsZi5fdGhyZXNob2xkLAogICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zPXNlbGYuX21heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAgICAgICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zPXNlbGYuX21pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgc2FtcGxpbmdfcmF0ZT1zZWxmLl9zYW1wbGluZ19yYXRlLAogICAgICAgICAgICAgICAgd2luZG93X3NpemVfc2FtcGxlcz1zZWxmLl93aW5kb3dfc2l6ZV9zYW1wbGVzLAogICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICkKCiAgICAgICAgIyBQZXIgY2hhbm5lbDoKICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IFtdCiAgICAgICAgZm9yIGNoYW5uZWwgaW4gYXVkaW86CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzLmFwcGVuZCgKICAgICAgICAgICAgICAgIHNlbGYuX2dldF9zcGVlY2hfdGltZXN0YW1wcygKICAgICAgICAgICAgICAgICAgICBjaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHNlbGYuX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZD1zZWxmLl90aHJlc2hvbGQsCiAgICAgICAgICAgICAgICAgICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tcz1zZWxmLl9taW5fc3BlZWNoX2R1cmF0aW9uX21zLAogICAgICAgICAgICAgICAgICAgIG1heF9zcGVlY2hfZHVyYXRpb25fcz1zZWxmLl9tYXhfc3BlZWNoX2R1cmF0aW9uX3MsCiAgICAgICAgICAgICAgICAgICAgbWluX3NpbGVuY2VfZHVyYXRpb25fbXM9c2VsZi5fbWluX3NpbGVuY2VfZHVyYXRpb25fbXMsCiAgICAgICAgICAgICAgICAgICAgc3BlZWNoX3BhZF9tcz1zZWxmLl9zcGVlY2hfcGFkX21zLAogICAgICAgICAgICAgICAgICAgIHNhbXBsaW5nX3JhdGU9c2VsZi5fc2FtcGxpbmdfcmF0ZSwKICAgICAgICAgICAgICAgICAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzPXNlbGYuX3dpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3NlY29uZHM9c2VsZi5fcmV0dXJuX3NlY29uZHMsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKCiAgICAgICAgcmV0dXJuIHNwZWVjaF90aW1lc3RhbXBzCgogICAgZGVmIF9yZWFkX2F1ZGlvKAogICAgICAgIHNlbGYsCiAgICAgICAgcGF0aDogUGF0aCwKICAgICkgLT4gdG9yY2guVGVuc29yOgogICAgICAgICIiIgogICAgICAgIFJlYWQgdGhlIGF1ZGlvIGZyb20gdGhlIGdpdmVuIHBhdGggYW5kIHJldHVybiBpdCBhcyBhIHRlbnNvci4KCiAgICAgICAgOnBhcmFtIHBhdGg6IFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlLgoKICAgICAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGFzIGEgdGVuc29yLgogICAgICAgICIiIgogICAgICAgICMgUmVhZCB0aGUgYXVkaW86CiAgICAgICAgYXVkaW8sIHNhbXBsaW5nX3JhdGUgPSB0b3JjaGF1ZGlvLmxvYWQoc3RyKHBhdGgpKQoKICAgICAgICAjIENoZWNrIGlmIHRoZSBhdWRpbyBpcyBzdGVyZW8gYW5kIGlmIHNvLCBjb252ZXJ0IGl0IHRvIG1vbm8gKG9ubHkgaWYgbm90IHBlciBjaGFubmVsKToKICAgICAgICBpZiBhdWRpby5zaXplKDApID4gMSBhbmQgbm90IHNlbGYuX3Blcl9jaGFubmVsOgogICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm1lYW4oZGltPTAsIGtlZXBkaW09VHJ1ZSkKCiAgICAgICAgIyBSZXNhbXBsZSB0aGUgYXVkaW8gaWYgbmVlZGVkOgogICAgICAgIGlmIHNhbXBsaW5nX3JhdGUgIT0gc2VsZi5fc2FtcGxpbmdfcmF0ZToKICAgICAgICAgICAgdHJhbnNmb3JtID0gdG9yY2hhdWRpby50cmFuc2Zvcm1zLlJlc2FtcGxlKAogICAgICAgICAgICAgICAgb3JpZ19mcmVxPXNhbXBsaW5nX3JhdGUsIG5ld19mcmVxPXNlbGYuX3NhbXBsaW5nX3JhdGUKICAgICAgICAgICAgKQogICAgICAgICAgICBhdWRpbyA9IHRyYW5zZm9ybShhdWRpbykKCiAgICAgICAgIyBSZXR1cm4gdGhlIGF1ZGlvIChzcXVlZXplIGlmIG5vdCBwZXIgY2hhbm5lbCk6CiAgICAgICAgcmV0dXJuIGF1ZGlvIGlmIHNlbGYuX3Blcl9jaGFubmVsIGVsc2UgYXVkaW8uc3F1ZWV6ZSgwKQoKCiM6IFRoZSB2YWx1ZSB0byBzZW5kIGludG8gbXVsdGlwcm9jZXNzaW5nIHF1ZXVlcyB0byBzdG9wIHRoZSBwcm9jZXNzOgpfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSyA9ICJTVE9QIgoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKAogICAgdmFkX2luaXRfa3dhcmdzOiBkaWN0LCB0YXNrc19xdWV1ZTogUXVldWUsIHJlc3VsdHNfcXVldWU6IFF1ZXVlCik6CiAgICAiIiIKICAgIENvbXBsZXRlIHRoZSB0YXNrcyBpbiB0aGUgZ2l2ZW4gcXVldWUgYW5kIHB1dCB0aGUgcmVzdWx0cyBpbiB0aGUgZ2l2ZW4gcmVzdWx0cyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcCB3aGVuCiAgICB0aGUgZ2l2ZW4gdGFza3MgcXVldWUgd2lsbCByZWNlaXZlIHRoZSBzdG9wIG1hcmsuIEl0IGlzIGFpbWVkIHRvIGJlIHVzZWQgd2l0aCBtdWx0aXByb2Nlc3NpbmcgYXMgYSBwcm9jZXNzLgoKICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga3dhcmdzLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogICBBIHF1ZXVlIHRvIHB1dCB0aGUgcmVzdWx0cyBpbi4KICAgICIiIgogICAgIyBJbml0aWFsaXplIGFuZCBsb2FkIHRoZSBWQUQ6CiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZChmb3JjZV9yZWxvYWQ9RmFsc2UpCgogICAgIyBTdGFydCBsaXN0ZW5pbmcgdG8gdGhlIHRhc2tzIHF1ZXVlOgogICAgd2hpbGUgVHJ1ZToKICAgICAgICAjIEdldCB0aGUgdGFzazoKICAgICAgICB0YXNrOiBUdXBsZVtzdHIsIGRpY3RdID0gdGFza3NfcXVldWUuZ2V0KCkKICAgICAgICBpZiB0YXNrID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSBUYXNrQ3JlYXRvci5mcm9tX3R1cGxlKHRhc2tfdHVwbGU9dGFzaykKICAgICAgICAgICAgIyBSdW4gdGhlIGZpbGUgdGhyb3VnaCB0aGUgVkFEOgogICAgICAgICAgICBzcGVlY2hfdGltZXN0YW1wcyA9IHZhZC5kZXRlY3Rfdm9pY2UoYXVkaW9fZmlsZT10YXNrLmF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBCdWlsZCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHQgPSAoRmFsc2UsIHRhc2suZ2V0X3Jlc3VsdCgpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIEJ1aWxkIHRoZSBlcnJvcjoKICAgICAgICAgICAgcmVzdWx0ID0gKFRydWUsICh0YXNrLmF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKQogICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0IC8gZXJyb3I6CiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQocmVzdWx0KQoKICAgICMgTWFyayB0aGUgZW5kIG9mIHRoZSB0YXNrczoKICAgIHJlc3VsdHNfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCiMgR2V0IHRoZSBnbG9iYWwgbG9nZ2VyOgp0cnk6CiAgICBpbXBvcnQgbWxydW4KCiAgICBfTE9HR0VSID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgoInNpbGVyb192YWQiKS5sb2dnZXIKZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICBfTE9HR0VSID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKCmRlZiBkZXRlY3Rfdm9pY2UoCiAgICAjIElucHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgICMgTW9kZWwgbG9hZGluZyBrd2FyZ3M6CiAgICB1c2Vfb25ueDogYm9vbCA9IFRydWUsCiAgICBmb3JjZV9vbm54X2NwdTogYm9vbCA9IFRydWUsCiAgICAjIERldGVjdGlvbiBrd2FyZ3M6CiAgICB0aHJlc2hvbGQ6IGZsb2F0ID0gMC41LAogICAgc2FtcGxpbmdfcmF0ZTogaW50ID0gMTZfMDAwLAogICAgbWluX3NwZWVjaF9kdXJhdGlvbl9tczogaW50ID0gMjUwLAogICAgbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiBmbG9hdCA9IGZsb2F0KCJpbmYiKSwKICAgIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBpbnQgPSAxMDAsCiAgICB3aW5kb3dfc2l6ZV9zYW1wbGVzOiBpbnQgPSA1MTIsCiAgICBzcGVlY2hfcGFkX21zOiBpbnQgPSAzMCwKICAgIHJldHVybl9zZWNvbmRzOiBib29sID0gRmFsc2UsCiAgICBwZXJfY2hhbm5lbDogYm9vbCA9IEZhbHNlLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHZvaWNlIGFjdGl2aXR5IGRldGVjdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtCiAgICBodHRwczovL2dpdGh1Yi5jb20vc25ha2VyczQvc2lsZXJvLXZhZC4gVGhlIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIKICAgIFZBRCB0aW1lc3RhbXBzIGRpY3Rpb25hcmllcyBhcyB2YWx1ZS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICB7InN0YXJ0IjogMCwgImVuZCI6IDE2MDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAxNjAwMCwgImVuZCI6IDMyMDAwfSwKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAzMjAwMCwgImVuZCI6IDQ4MDAwfSwKICAgICAgICAgICAgICAgIC4uLgogICAgICAgICAgICBdLAogICAgICAgICAgICAiZmlsZV8yLndhdiI6IFsKICAgICAgICAgICAgICAgIHsic3RhcnQiOiAwLCAiZW5kIjogMTYwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDE2MDAwLCAiZW5kIjogMzIwMDB9LAogICAgICAgICAgICAgICAgeyJzdGFydCI6IDMyMDAwLCAiZW5kIjogNDgwMDB9LAogICAgICAgICAgICAgICAgLi4uCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgIC4uLgogICAgICAgIH0KCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgICAgICBUaGUgcGF0aCB0byB0aGUgYXVkaW8gZmlsZXMgdG8gZGlhcml6ZS4gQ2FuIGJlIGEgcGF0aCB0byBhIHNpbmdsZSBmaWxlLCBhIHBhdGggdG8gYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rvcnkgb3IgYSBsaXN0IG9mIHBhdGhzIHRvIGZpbGVzLgogICAgOnBhcmFtIHVzZV9vbm54OiAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHVzZSBPTk5YIGZvciBpbmZlcmVuY2UuIERlZmF1bHQgaXMgVHJ1ZS4KICAgIDpwYXJhbSBmb3JjZV9vbm54X2NwdTogICAgICAgICAgV2hldGhlciB0byBmb3JjZSBPTk5YIHRvIHVzZSBDUFUgZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIHRocmVzaG9sZDogICAgICAgICAgICAgICBTcGVlY2ggdGhyZXNob2xkLiBTaWxlcm8gVkFEIG91dHB1dHMgc3BlZWNoIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggYXVkaW8gY2h1bmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JhYmlsaXRpZXMgQUJPVkUgdGhpcyB2YWx1ZSBhcmUgY29uc2lkZXJlZCBhcyBTUEVFQ0guIEl0IGlzIGJldHRlciB0byB0dW5lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMgcGFyYW1ldGVyIGZvciBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseSwgYnV0ICJsYXp5IiAwLjUgaXMgcHJldHR5IGdvb2QgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vc3QgZGF0YXNldHMuCiAgICA6cGFyYW0gc2FtcGxpbmdfcmF0ZTogICAgICAgICAgIEN1cnJlbnRseSwgc2lsZXJvIFZBRCBtb2RlbHMgc3VwcG9ydCA4MDAwIGFuZCAxNjAwMCBzYW1wbGUgcmF0ZXMuCiAgICA6cGFyYW0gbWluX3NwZWVjaF9kdXJhdGlvbl9tczogIEZpbmFsIHNwZWVjaCBjaHVua3Mgc2hvcnRlciBtaW5fc3BlZWNoX2R1cmF0aW9uX21zIGFyZSB0aHJvd24gb3V0LgogICAgOnBhcmFtIG1heF9zcGVlY2hfZHVyYXRpb25fczogICBNYXhpbXVtIGR1cmF0aW9uIG9mIHNwZWVjaCBjaHVua3MgaW4gc2Vjb25kcy4gQ2h1bmtzIGxvbmdlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBtYXhfc3BlZWNoX2R1cmF0aW9uX3NgIHdpbGwgYmUgc3BsaXQgYXQgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbGFzdCBzaWxlbmNlIHRoYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHMgbW9yZSB0aGFuIDEwMG1zIChpZiBhbnkpLCB0byBwcmV2ZW50IGFnZ3Jlc3NpdmUgY3V0dGluZy4gT3RoZXJ3aXNlLCB0aGV5IHdpbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmUgc3BsaXQgYWdncmVzc2l2ZWx5IGp1c3QgYmVmb3JlIG1heF9zcGVlY2hfZHVyYXRpb25fcy4KICAgIDpwYXJhbSBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogSW4gdGhlIGVuZCBvZiBlYWNoIHNwZWVjaCBjaHVuayB3YWl0IGZvciBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyBiZWZvcmUgc2VwYXJhdGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB3aW5kb3dfc2l6ZV9zYW1wbGVzOiAgICAgQXVkaW8gY2h1bmtzIG9mIHdpbmRvd19zaXplX3NhbXBsZXMgc2l6ZSBhcmUgZmVkIHRvIHRoZSBzaWxlcm8gVkFEIG1vZGVsLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0FSTklORyEgU2lsZXJvIFZBRCBtb2RlbHMgd2VyZSB0cmFpbmVkIHVzaW5nIDUxMiwgMTAyNCwgMTUzNiBzYW1wbGVzIGZvciAxNjAwMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgcmF0ZSBhbmQgMjU2LCA1MTIsIDc2OCBzYW1wbGVzIGZvciA4MDAwIHNhbXBsZSByYXRlLiBWYWx1ZXMgb3RoZXIgdGhhbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVzZSBtYXkgYWZmZWN0IG1vZGVsIHBlcmZvcm1hbmNlIQogICAgOnBhcmFtIHNwZWVjaF9wYWRfbXM6ICAgICAgICAgICBGaW5hbCBzcGVlY2ggY2h1bmtzIGFyZSBwYWRkZWQgYnkgc3BlZWNoX3BhZF9tcyBlYWNoIHNpZGUuCiAgICA6cGFyYW0gcmV0dXJuX3NlY29uZHM6ICAgICAgICAgIFdoZXRoZXIgcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2Vjb25kcy4gRmFsc2UgbWVhbnMgdG8gcmV0dXJuIHRpbWVzdGFtcHMgaW4gc2FtcGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGVmYXVsdCAtIEZhbHNlKS4KICAgIDpwYXJhbSBwZXJfY2hhbm5lbDogICAgICAgICAgICAgV2hldGhlciB0byByZXR1cm4gdGltZXN0YW1wcyBwZXIgY2hhbm5lbCAoZGVmYXVsdCAtIEZhbHNlKS4gVGhpcyB3aWxsIHJ1biBWQUQgb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWFjaCBjaGFubmVsIHNlcGFyYXRlbHkgYW5kIHJldHVybiBhIGxpc3Qgb2YgdGltZXN0YW1wcyBwZXIgY2hhbm5lbC4KICAgIDpwYXJhbSB1c2VfbXVsdGlwcm9jZXNzaW5nOiAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZSBmb3IgbXVsdGlwcm9jZXNzaW5nLiBJZiAwLCBubyBtdWx0aXByb2Nlc3Npbmcgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB1c2VkLiBEZWZhdWx0IGlzIDAuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICAgICAgIFZlcmJvc2l0eS4KICAgICIiIgogICAgZ2xvYmFsIF9MT0dHRVIKCiAgICAjIEdldCB0aGUgaW5wdXQgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJDb2xsZWN0aW5nIGF1ZGlvIGZpbGVzLiIpCiAgICBhdWRpb19maWxlcyA9IF9nZXRfYXVkaW9fZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiQ29sbGVjdGVkIHtsZW4oYXVkaW9fZmlsZXMpfSBhdWRpbyBmaWxlcy4iKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIHZhZF9pbml0X2t3YXJncyA9IHsKICAgICAgICAidXNlX29ubngiOiB1c2Vfb25ueCwKICAgICAgICAiZm9yY2Vfb25ueF9jcHUiOiBmb3JjZV9vbm54X2NwdSwKICAgICAgICAidGhyZXNob2xkIjogdGhyZXNob2xkLAogICAgICAgICJzYW1wbGluZ19yYXRlIjogc2FtcGxpbmdfcmF0ZSwKICAgICAgICAibWluX3NwZWVjaF9kdXJhdGlvbl9tcyI6IG1pbl9zcGVlY2hfZHVyYXRpb25fbXMsCiAgICAgICAgIm1heF9zcGVlY2hfZHVyYXRpb25fcyI6IG1heF9zcGVlY2hfZHVyYXRpb25fcywKICAgICAgICAibWluX3NpbGVuY2VfZHVyYXRpb25fbXMiOiBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcywKICAgICAgICAid2luZG93X3NpemVfc2FtcGxlcyI6IHdpbmRvd19zaXplX3NhbXBsZXMsCiAgICAgICAgInNwZWVjaF9wYWRfbXMiOiBzcGVlY2hfcGFkX21zLAogICAgICAgICJyZXR1cm5fc2Vjb25kcyI6IHJldHVybl9zZWNvbmRzLAogICAgICAgICJwZXJfY2hhbm5lbCI6IHBlcl9jaGFubmVsLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcih0YXNrX3R5cGU9QmFzZVRhc2spCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEZXRlY3Rpbmcgdm9pY2UiLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBkaWFyaXplKAogICAgIyBJbnB1dCAvIE91dHB1dCBrd2FyZ3M6CiAgICBkYXRhX3BhdGg6IFVuaW9uW3N0ciwgUGF0aCwgTGlzdFtVbmlvbltzdHIsIFBhdGhdXV0sCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgdXNlX29ubng6IGJvb2wgPSBUcnVlLAogICAgZm9yY2Vfb25ueF9jcHU6IGJvb2wgPSBUcnVlLAogICAgIyBEZXRlY3Rpb24ga3dhcmdzOgogICAgdGhyZXNob2xkOiBmbG9hdCA9IDAuNSwKICAgIHNhbXBsaW5nX3JhdGU6IGludCA9IDE2XzAwMCwKICAgIG1pbl9zcGVlY2hfZHVyYXRpb25fbXM6IGludCA9IDI1MCwKICAgIG1heF9zcGVlY2hfZHVyYXRpb25fczogZmxvYXQgPSBmbG9hdCgiaW5mIiksCiAgICBtaW5fc2lsZW5jZV9kdXJhdGlvbl9tczogaW50ID0gMTAwLAogICAgd2luZG93X3NpemVfc2FtcGxlczogaW50ID0gNTEyLAogICAgc3BlZWNoX3BhZF9tczogaW50ID0gMzAsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWFrZXJfbGFiZWxzOiBMaXN0W3N0cl0gPSBOb25lLAogICAgIyBPdGhlciBrd2FyZ3M6CiAgICB1c2VfbXVsdGlwcm9jZXNzaW5nOiBpbnQgPSAwLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBQZXJmb3JtIHNwZWVjaCBkaWFyaXphdGlvbiBvbiBnaXZlbiBhdWRpbyBmaWxlcyB1c2luZyB0aGUgc2lsZXJvIFZBRCBtb2RlbCAtIGh0dHBzOi8vZ2l0aHViLmNvbS9zbmFrZXJzNC9zaWxlcm8tdmFkLgogICAgVGhlIHNwZWVjaCBkaWFyaXphdGlvbiBpcyBwZXJmb3JtZWQgcGVyIGNoYW5uZWwgc28gdGhhdCBlYWNoIGNoYW5uZWwgaW4gdGhlIGF1ZGlvIGJlbG9uZyB0byBhIGRpZmZlcmVudCBzcGVha2VyLiBUaGUKICAgIGVuZCByZXN1bHQgaXMgYSBkaWN0aW9uYXJ5IHdpdGggdGhlIGZpbGUgbmFtZXMgYXMga2V5cyBhbmQgdGhlaXIgZGlhcml6YXRpb24gYXMgdmFsdWUuIEEgZGlhcml6YXRpb24gaXMgYSBsaXN0CiAgICBvZiB0dXBsZXM6IChzdGFydCwgZW5kLCBzcGVha2VyX2xhYmVsKS4KCiAgICBGb3IgZXhhbXBsZTo6CgogICAgICAgIHsKICAgICAgICAgICAgImZpbGVfMS53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgImZpbGVfMi53YXYiOiBbCiAgICAgICAgICAgICAgICAoMC4wLCAxLjAsICJzcGVha2VyXzAiKSwKICAgICAgICAgICAgICAgICgxLjAsIDIuMCwgInNwZWFrZXJfMSIpLAogICAgICAgICAgICAgICAgKDIuMCwgMy4wLCAic3BlYWtlcl8wIiksCiAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgXSwKICAgICAgICAgICAgLi4uCiAgICAgICAgfQoKCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICAgICAgIFRoZSBwYXRoIHRvIHRoZSBhdWRpbyBmaWxlcyB0byBkaWFyaXplLiBDYW4gYmUgYSBwYXRoIHRvIGEgc2luZ2xlIGZpbGUsIGEgcGF0aCB0byBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdG9yeSBvciBhIGxpc3Qgb2YgcGF0aHMgdG8gZmlsZXMuCiAgICA6cGFyYW0gdXNlX29ubng6ICAgICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIE9OTlggZm9yIGluZmVyZW5jZS4gRGVmYXVsdCBpcyBUcnVlLgogICAgOnBhcmFtIGZvcmNlX29ubnhfY3B1OiAgICAgICAgICBXaGV0aGVyIHRvIGZvcmNlIE9OTlggdG8gdXNlIENQVSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIFRydWUuCiAgICA6cGFyYW0gdGhyZXNob2xkOiAgICAgICAgICAgICAgIFNwZWVjaCB0aHJlc2hvbGQuIFNpbGVybyBWQUQgb3V0cHV0cyBzcGVlY2ggcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBhdWRpbyBjaHVuaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYmFiaWxpdGllcyBBQk9WRSB0aGlzIHZhbHVlIGFyZSBjb25zaWRlcmVkIGFzIFNQRUVDSC4gSXQgaXMgYmV0dGVyIHRvIHR1bmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcyBwYXJhbWV0ZXIgZm9yIGVhY2ggZGF0YXNldCBzZXBhcmF0ZWx5LCBidXQgImxhenkiIDAuNSBpcyBwcmV0dHkgZ29vZCBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9zdCBkYXRhc2V0cy4KICAgIDpwYXJhbSBzYW1wbGluZ19yYXRlOiAgICAgICAgICAgQ3VycmVudGx5LCBzaWxlcm8gVkFEIG1vZGVscyBzdXBwb3J0IDgwMDAgYW5kIDE2MDAwIHNhbXBsZSByYXRlcy4KICAgIDpwYXJhbSBtaW5fc3BlZWNoX2R1cmF0aW9uX21zOiAgRmluYWwgc3BlZWNoIGNodW5rcyBzaG9ydGVyIG1pbl9zcGVlY2hfZHVyYXRpb25fbXMgYXJlIHRocm93biBvdXQuCiAgICA6cGFyYW0gbWF4X3NwZWVjaF9kdXJhdGlvbl9zOiAgIE1heGltdW0gZHVyYXRpb24gb2Ygc3BlZWNoIGNodW5rcyBpbiBzZWNvbmRzLiBDaHVua3MgbG9uZ2VyIHRoYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYG1heF9zcGVlY2hfZHVyYXRpb25fc2Agd2lsbCBiZSBzcGxpdCBhdCB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXN0IHNpbGVuY2UgdGhhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0cyBtb3JlIHRoYW4gMTAwbXMgKGlmIGFueSksIHRvIHByZXZlbnQgYWdncmVzc2l2ZSBjdXR0aW5nLiBPdGhlcndpc2UsIHRoZXkgd2lsbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSBzcGxpdCBhZ2dyZXNzaXZlbHkganVzdCBiZWZvcmUgbWF4X3NwZWVjaF9kdXJhdGlvbl9zLgogICAgOnBhcmFtIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zOiBJbiB0aGUgZW5kIG9mIGVhY2ggc3BlZWNoIGNodW5rIHdhaXQgZm9yIG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zIGJlZm9yZSBzZXBhcmF0aW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0LgogICAgOnBhcmFtIHdpbmRvd19zaXplX3NhbXBsZXM6ICAgICBBdWRpbyBjaHVua3Mgb2Ygd2luZG93X3NpemVfc2FtcGxlcyBzaXplIGFyZSBmZWQgdG8gdGhlIHNpbGVybyBWQUQgbW9kZWwuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXQVJOSU5HISBTaWxlcm8gVkFEIG1vZGVscyB3ZXJlIHRyYWluZWQgdXNpbmcgNTEyLCAxMDI0LCAxNTM2IHNhbXBsZXMgZm9yIDE2MDAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSByYXRlIGFuZCAyNTYsIDUxMiwgNzY4IHNhbXBsZXMgZm9yIDgwMDAgc2FtcGxlIHJhdGUuIFZhbHVlcyBvdGhlciB0aGFuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZXNlIG1heSBhZmZlY3QgbW9kZWwgcGVyZm9ybWFuY2UhCiAgICA6cGFyYW0gc3BlZWNoX3BhZF9tczogICAgICAgICAgIEZpbmFsIHNwZWVjaCBjaHVua3MgYXJlIHBhZGRlZCBieSBzcGVlY2hfcGFkX21zIGVhY2ggc2lkZS4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgVGhlIHNwZWFrZXIgbGFiZWxzIHRvIHVzZSBmb3IgdGhlIGRpYXJpemF0aW9uLiBJZiBub3QgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkICJzcGVha2VyXzAiLCAic3BlYWtlcl8xIiwgZXRjLgogICAgOnBhcmFtIHVzZV9tdWx0aXByb2Nlc3Npbmc6ICAgICBUaGUgbnVtYmVyIG9mIHdvcmtlcnMgdG8gdXNlIGZvciBtdWx0aXByb2Nlc3NpbmcuIElmIDAsIG5vIG11bHRpcHJvY2Vzc2luZyB3aWxsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlIHVzZWQuIERlZmF1bHQgaXMgMC4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICAgICAgVmVyYm9zaXR5LgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBJbml0aWFsaXplIHRoZSB0cmFuc2NyaXB0aW9uIHBpcGVsaW5lOgogICAgdmFkX2luaXRfa3dhcmdzID0gewogICAgICAgICJ1c2Vfb25ueCI6IHVzZV9vbm54LAogICAgICAgICJmb3JjZV9vbm54X2NwdSI6IGZvcmNlX29ubnhfY3B1LAogICAgICAgICJ0aHJlc2hvbGQiOiB0aHJlc2hvbGQsCiAgICAgICAgInNhbXBsaW5nX3JhdGUiOiBzYW1wbGluZ19yYXRlLAogICAgICAgICJtaW5fc3BlZWNoX2R1cmF0aW9uX21zIjogbWluX3NwZWVjaF9kdXJhdGlvbl9tcywKICAgICAgICAibWF4X3NwZWVjaF9kdXJhdGlvbl9zIjogbWF4X3NwZWVjaF9kdXJhdGlvbl9zLAogICAgICAgICJtaW5fc2lsZW5jZV9kdXJhdGlvbl9tcyI6IG1pbl9zaWxlbmNlX2R1cmF0aW9uX21zLAogICAgICAgICJ3aW5kb3dfc2l6ZV9zYW1wbGVzIjogd2luZG93X3NpemVfc2FtcGxlcywKICAgICAgICAic3BlZWNoX3BhZF9tcyI6IHNwZWVjaF9wYWRfbXMsCiAgICAgICAgInJldHVybl9zZWNvbmRzIjogVHJ1ZSwKICAgICAgICAicGVyX2NoYW5uZWwiOiBUcnVlLAogICAgfQoKICAgICMgQ3JlYXRlIHRoZSB0YXNrIGNyZWF0b3I6CiAgICB0YXNrX2NyZWF0b3IgPSBUYXNrQ3JlYXRvcigKICAgICAgICB0YXNrX3R5cGU9U3BlZWNoRGlhcml6YXRpb25UYXNrLCB0YXNrX2t3YXJncz17InNwZWFrZXJfbGFiZWxzIjogc3BlYWtlcl9sYWJlbHN9CiAgICApCgogICAgIyBSdW4gdGhlIHRyYW5zY3JpcHRpb246CiAgICBpZiB1c2VfbXVsdGlwcm9jZXNzaW5nOgogICAgICAgIHJlc3VsdHMgPSBfcGFyYWxsZWxfcnVuKAogICAgICAgICAgICBuX3dvcmtlcnM9dXNlX211bHRpcHJvY2Vzc2luZywKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJEaWFyaXppbmciLAogICAgICAgICAgICB2YWRfaW5pdF9rd2FyZ3M9dmFkX2luaXRfa3dhcmdzLAogICAgICAgICAgICB0YXNrX2NyZWF0b3I9dGFza19jcmVhdG9yLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIHJldHVybiBfcHJvY2Vzc19yZXN1bHRzKHJlc3VsdHM9cmVzdWx0cywgdmVyYm9zZT12ZXJib3NlKQoKCmRlZiBfZ2V0X2F1ZGlvX2ZpbGVzKAogICAgZGF0YV9wYXRoOiBVbmlvbltQYXRoLCBzdHIsIGxpc3RdLAopIC0+IExpc3RbUGF0aF06CiAgICAiIiIKICAgIEdldCB0aGUgYXVkaW8gZmlsZXMgZnJvbSB0aGUgZGF0YSBwYXRoLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUKICAgIGNvbGxlY3RlZC4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiBUaGUgZGF0YSBwYXRoIHRvIGNvbGxlY3QgdGhlIGF1ZGlvIGZpbGVzIGZyb20uCgogICAgOnJldHVybnM6IFRoZSBhdWRpbyBmaWxlcyBsaXN0LgogICAgIiIiCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgbGlzdCBvZiBwYXRoczoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBsaXN0KToKICAgICAgICBhdWRpb19maWxlcyA9IFtdCiAgICAgICAgZm9yIHBhdGggaW4gZGF0YV9wYXRoOgogICAgICAgICAgICBhdWRpb19maWxlcy5leHRlbmQoX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9cGF0aCkpCiAgICAgICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgogICAgIyBDaGVjayBpZiBnaXZlbiBhIHNpbmdsZSBzdHJpbmcgcGF0aCB0byBjYXN0IGl0IHRvIGEgYHBhdGhsaWIuUGF0aGA6CiAgICBpZiBpc2luc3RhbmNlKGRhdGFfcGF0aCwgc3RyKToKICAgICAgICBkYXRhX3BhdGggPSBQYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQoKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICBhdWRpb19maWxlcyA9IGxpc3QoZGF0YV9wYXRoLmdsb2IoIiouKiIpKQogICAgZWxpZiBkYXRhX3BhdGguaXNfZmlsZSgpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW2RhdGFfcGF0aF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgZiJVbnJlY29nbml6ZWQgZGF0YSBwYXRoLiBUaGUgcGFyYW1ldGVyIGBkYXRhX3BhdGhgIG11c3QgYmUgYSB2YWxpZCBwYXRoIHRvIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgIgogICAgICAgICAgICBmImZpbGUuIEdpdmVuOiB7c3RyKGRhdGFfcGF0aCl9ICIKICAgICAgICApCgogICAgcmV0dXJuIGF1ZGlvX2ZpbGVzCgoKZGVmIF9ydW4oCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgTG9hZCBhIFZBRCBhbmQgdXNlIGl0IHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcgdGhlIGdpdmVuIHRhc2sgY3JlYXRvci4KCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdXNlLgogICAgOnBhcmFtIGRlc2NyaXB0aW9uOiAgICAgVGhlIGRlc2NyaXB0aW9uIHRvIHVzZSBmb3IgdGhlIHByb2dyZXNzIGJhci4KICAgIDpwYXJhbSB2YWRfaW5pdF9rd2FyZ3M6IFRoZSBWQUQgaW5pdGlhbGl6YXRpb24ga2V5d29yZCBhcmd1bWVudHMuCiAgICA6cGFyYW0gdGFza19jcmVhdG9yOiAgICBUaGUgdGFzayBjcmVhdG9yIHRvIHVzZSB0byBjcmVhdGUgdGhlIHRhc2tzLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgVkFEOgogICAgdmFkID0gVm9pY2VBY3Rpdml0eURldGVjdG9yKCoqdmFkX2luaXRfa3dhcmdzKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJMb2FkaW5nIHRoZSBWQUQgbW9kZWwuIikKICAgIHZhZC5sb2FkKCkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJWQUQgbW9kZWwgbG9hZGVkLiIpCgogICAgIyBSdW4gdGhlIFZBRCBvbiB0aGUgYXVkaW8gZmlsZXMgYW5kIGNvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIGZvciBhdWRpb19maWxlIGluIHRxZG0oCiAgICAgICAgYXVkaW9fZmlsZXMsCiAgICAgICAgZGVzYz1kZXNjcmlwdGlvbiwKICAgICAgICB1bml0PSJmaWxlIiwKICAgICAgICB0b3RhbD1sZW4oYXVkaW9fZmlsZXMpLAogICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICApOgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBDcmVhdGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2sgPSB0YXNrX2NyZWF0b3IuY3JlYXRlX3Rhc2soYXVkaW9fZmlsZT1hdWRpb19maWxlKQogICAgICAgICAgICAjIFJ1biB0aGUgZmlsZSB0aHJvdWdoIHRoZSBWQUQ6CiAgICAgICAgICAgIHNwZWVjaF90aW1lc3RhbXBzID0gdmFkLmRldGVjdF92b2ljZShhdWRpb19maWxlPWF1ZGlvX2ZpbGUpCiAgICAgICAgICAgICMgQ29tcGxldGUgdGhlIHRhc2s6CiAgICAgICAgICAgIHRhc2suZG9fdGFzayhzcGVlY2hfdGltZXN0YW1wcz1zcGVlY2hfdGltZXN0YW1wcykKICAgICAgICAgICAgIyBDb2xsZWN0IHRoZSByZXN1bHQ6CiAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKChGYWxzZSwgdGFzay5nZXRfcmVzdWx0KCkpKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIGVycm9yOgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZCgoVHJ1ZSwgKGF1ZGlvX2ZpbGUubmFtZSwgc3RyKGV4Y2VwdGlvbikpKSkKCiAgICByZXR1cm4gcmVzdWx0cwoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGRlc2NyaXB0aW9uOiBzdHIsCiAgICB2YWRfaW5pdF9rd2FyZ3M6IGRpY3QsCiAgICB0YXNrX2NyZWF0b3I6IFRhc2tDcmVhdG9yLAogICAgdmVyYm9zZTogYm9vbCwKKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgbGlzdF1dXToKICAgICIiIgogICAgUnVuIG11bHRpcGxlIFZBRCB3b3JrZXJzIHdpdGggbXVsdGlwcm9jZXNzaW5nIHRvIGNvbXBsZXRlIHRoZSB0YXNrcyB0aGF0IHdpbGwgYmUgY3JlYXRlZCBvbiB0aGUgcHJvdmlkZWQgZmlsZXMgdXNpbmcKICAgIHRoZSBnaXZlbiB0YXNrIGNyZWF0b3IuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHVzZS4KICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB1c2UuCiAgICA6cGFyYW0gZGVzY3JpcHRpb246ICAgICBUaGUgZGVzY3JpcHRpb24gdG8gdXNlIGZvciB0aGUgcHJvZ3Jlc3MgYmFyLgogICAgOnBhcmFtIHZhZF9pbml0X2t3YXJnczogVGhlIFZBRCBpbml0aWFsaXphdGlvbiBrZXl3b3JkIGFyZ3VtZW50cy4KICAgIDpwYXJhbSB0YXNrX2NyZWF0b3I6ICAgIFRoZSB0YXNrIGNyZWF0b3IgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgdGFza3MuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBWZXJib3NpdHkuCgogICAgOnJldHVybnM6IFRoZSBjb2xsZWN0ZWQgcmVzdWx0cy4KICAgICIiIgogICAgIyBMb2FkIHRoZSBWQUQgKGRvd25sb2FkIG9uY2UsIGFuZCBpdCB3aWxsIGJlIGxvYWRlZCB0aGVuIHBlciBwcm9jZXNzIGxhdGVyIG9uKToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgVkFEIG1vZGVsLiIpCiAgICB2YWQgPSBWb2ljZUFjdGl2aXR5RGV0ZWN0b3IoKip2YWRfaW5pdF9rd2FyZ3MpCiAgICB2YWQubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVkFEIG1vZGVsIGxvYWRlZC4iKQoKICAgICMgQ2hlY2sgdGhlIG51bWJlciBvZiB3b3JrZXJzOgogICAgaWYgbl93b3JrZXJzID4gbGVuKGF1ZGlvX2ZpbGVzKToKICAgICAgICBfTE9HR0VSLndhcm5pbmcoCiAgICAgICAgICAgIGYiVGhlIG51bWJlciBvZiB3b3JrZXJzICh7bl93b3JrZXJzfSkgaXMgbGFyZ2VyIHRoYW4gdGhlIG51bWJlciBvZiBhdWRpbyBmaWxlcyAoe2xlbihhdWRpb19maWxlcyl9KS4gIgogICAgICAgICAgICBmIlNldHRpbmcgdGhlIG51bWJlciBvZiB3b3JrZXJzIHRvIHtsZW4oYXVkaW9fZmlsZXMpfS4iCiAgICAgICAgKQogICAgICAgIG5fd29ya2VycyA9IGxlbihhdWRpb19maWxlcykKCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICB0YXNrc19xdWV1ZSA9IFF1ZXVlKCkKICAgIHJlc3VsdHNfcXVldWUgPSBRdWV1ZSgpCgogICAgIyBJbml0aWFsaXplIHRoZSBtdWx0aXByb2Nlc3NpbmcgcHJvY2Vzc2VzOgogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsKICAgICAgICAgICAgICAgICJ2YWRfaW5pdF9rd2FyZ3MiOiB2YWRfaW5pdF9rd2FyZ3MsCiAgICAgICAgICAgICAgICAidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwKICAgICAgICAgICAgICAgICJyZXN1bHRzX3F1ZXVlIjogcmVzdWx0c19xdWV1ZSwKICAgICAgICAgICAgfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuc3RhcnQoKQoKICAgICMgUHV0IHRoZSB0YXNrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICB0YXNrc19xdWV1ZS5wdXQodGFza19jcmVhdG9yLmNyZWF0ZV90YXNrKGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSkudG9fdHVwbGUoKSkKCiAgICAjIFB1dCB0aGUgc3RvcCBtYXJrcyBpbiB0aGUgcXVldWU6CiAgICBmb3IgXyBpbiByYW5nZShuX3dvcmtlcnMpOgogICAgICAgIHRhc2tzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAjIENvbGxlY3QgdGhlIHJlc3VsdHM6CiAgICByZXN1bHRzID0gW10KICAgIHN0b3BfbWFya3NfY291bnRlciA9IDAKICAgIHdpdGggdHFkbSgKICAgICAgICBkZXNjPWRlc2NyaXB0aW9uLAogICAgICAgIHVuaXQ9ImZpbGUiLAogICAgICAgIHRvdGFsPWxlbihhdWRpb19maWxlcyksCiAgICAgICAgZGlzYWJsZT1ub3QgdmVyYm9zZSwKICAgICkgYXMgcHJvZ3Jlc3NiYXI6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgIyBHZXQgYSByZXN1bHQgZnJvbSB0aGUgcXVldWU6CiAgICAgICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV0gPSByZXN1bHRzX3F1ZXVlLmdldCgpCiAgICAgICAgICAgIGlmIHJlc3VsdCA9PSBfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSzoKICAgICAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgICAgICBpZiBzdG9wX21hcmtzX2NvdW50ZXIgPT0gbl93b3JrZXJzOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHJlc3VsdDoKICAgICAgICAgICAgICAgIHJlc3VsdHMuYXBwZW5kKHJlc3VsdCkKICAgICAgICAgICAgICAgIHByb2dyZXNzYmFyLnVwZGF0ZSgxKQoKICAgICMgV2FpdCBmb3IgdGhlIHByb2Nlc3NlcyB0byBmaW5pc2g6CiAgICBmb3IgcCBpbiB0YXNrX2NvbXBsZXRpb25fcHJvY2Vzc2VzOgogICAgICAgIHAuam9pbigpCgogICAgcmV0dXJuIHJlc3VsdHMKCgpkZWYgX3Byb2Nlc3NfcmVzdWx0cygKICAgIHJlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBsaXN0XV1dLCB2ZXJib3NlOiBib29sCikgLT4gVHVwbGVbZGljdCwgZGljdF06CiAgICAiIiIKICAgIFByb2Nlc3MgdGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgoKICAgIDpwYXJhbSByZXN1bHRzOiBUaGUgcmVzdWx0cyB0byBwcm9jZXNzLgogICAgOnBhcmFtIHZlcmJvc2U6IFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIHByb2Nlc3NlZCByZXN1bHRzIGFzIGEgdHVwbGUgb2Ygc3VjY2Vzc2VzIGFuZCBlcnJvcnMuCiAgICAiIiIKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0ge30KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlc1tyZXN1bHRbMF1dID0gcmVzdWx0WzFdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkRvbmUgKHtsZW4oc3VjY2Vzc2VzKX0ve2xlbihzdWNjZXNzZXMpICsgbGVuKGVycm9ycyl9KVxuIikKCiAgICByZXR1cm4gc3VjY2Vzc2VzLCBlcnJvcnMK
    +    origin_filename: ''
    +  image: ''
    +  command: ''
       entry_points:
         audio_file:
    -      name: audio_file
           doc: Get the audio file of the task.
    -      parameters:
    -      - name: self
    +      lineno: 43
    +      has_varargs: false
           outputs:
           - doc: The audio file of the task.
             type: Path
    -      lineno: 43
    -      has_varargs: false
    +      parameters:
    +      - name: self
           has_kwargs: false
    +      name: audio_file
         do_task:
    -      name: do_task
           doc: Do the task on the given speech timestamps. The task will diarize the VAD
             speech timestamps into speakers.
    +      lineno: 94
    +      has_varargs: false
           parameters:
           - name: self
           - name: speech_timestamps
             type: List[List[Dict[str, int]]]
             doc: The speech timestamps per channel to do the task on as outputted from
               the VAD.
    -      outputs: []
    -      lineno: 94
    -      has_varargs: false
           has_kwargs: false
    +      name: do_task
         get_result:
    -      name: get_result
           doc: Get the result of the task. A tuple of the audio file name and the result.
    -      parameters:
    -      - name: self
    +      lineno: 61
    +      has_varargs: false
           outputs:
           - doc: The result of the task.
             type: Tuple[str, list]
    -      lineno: 61
    -      has_varargs: false
    +      parameters:
    +      - name: self
           has_kwargs: false
    +      name: get_result
         to_tuple:
    -      name: to_tuple
           doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing
             to pass in queue).
    -      parameters:
    -      - name: self
    +      lineno: 116
    +      has_varargs: false
           outputs:
           - doc: The converted task.
             type: Tuple[str, dict]
    -      lineno: 116
    -      has_varargs: false
    +      parameters:
    +      - name: self
           has_kwargs: false
    +      name: to_tuple
         create_task:
    -      name: create_task
           doc: Create a task with the given audio file.
    +      lineno: 146
    +      has_varargs: false
    +      outputs:
    +      - doc: The created task.
    +        type: BaseTask
           parameters:
           - name: self
           - name: audio_file
             type: Path
             doc: The audio file to assign to the task.
    -      outputs:
    -      - doc: The created task.
    -        type: BaseTask
    -      lineno: 146
    -      has_varargs: false
           has_kwargs: false
    +      name: create_task
         from_tuple:
    -      name: from_tuple
           doc: Create a task from a tuple of the audio file name and the task kwargs.
    +      lineno: 157
    +      has_varargs: false
    +      outputs:
    +      - doc: The created task.
    +        type: BaseTask
           parameters:
           - name: cls
           - name: task_tuple
             type: Tuple[str, dict]
             doc: The task tuple to create the task from.
    -      outputs:
    -      - doc: The created task.
    -        type: BaseTask
    -      lineno: 157
    -      has_varargs: false
           has_kwargs: false
    +      name: from_tuple
         load:
    -      name: load
           doc: Load the VAD model.
    +      lineno: 234
    +      has_varargs: false
           parameters:
           - name: self
           - name: force_reload
    @@ -142,12 +136,9 @@
             doc: Whether to force reload the model even if it was already loaded. Default
               is True.
             default: true
    -      outputs: []
    -      lineno: 234
    -      has_varargs: false
           has_kwargs: false
    +      name: load
         detect_voice:
    -      name: detect_voice
           doc: "Perform voice activity detection on given audio files using the silero\
             \ VAD model -\nhttps://github.com/snakers4/silero-vad. The end result is a\
             \ dictionary with the file names as keys and their\nVAD timestamps dictionaries\
    @@ -158,6 +149,8 @@
             : 16000},\n            {\"start\": 16000, \"end\": 32000},\n            {\"\
             start\": 32000, \"end\": 48000},\n            ...\n        ],\n        ...\n\
             \    }"
    +      lineno: 393
    +      has_varargs: false
           parameters:
           - name: data_path
             type: Union[str, Path, List[Union[str, Path]]]
    @@ -225,12 +218,9 @@
             type: bool
             doc: Verbosity.
             default: false
    -      outputs: []
    -      lineno: 393
    -      has_varargs: false
           has_kwargs: false
    +      name: detect_voice
         diarize:
    -      name: diarize
           doc: "Perform speech diarization on given audio files using the silero VAD model\
             \ - https://github.com/snakers4/silero-vad.\nThe speech diarization is performed\
             \ per channel so that each channel in the audio belong to a different speaker.\
    @@ -242,6 +232,8 @@
             : [\n            (0.0, 1.0, \"speaker_0\"),\n            (1.0, 2.0, \"speaker_1\"\
             ),\n            (2.0, 3.0, \"speaker_0\"),\n            ...\n        ],\n\
             \        ...\n    }"
    +      lineno: 517
    +      has_varargs: false
           parameters:
           - name: data_path
             type: Union[str, Path, List[Union[str, Path]]]
    @@ -304,21 +296,11 @@
             type: bool
             doc: Verbosity.
             default: false
    -      outputs: []
    -      lineno: 517
    -      has_varargs: false
           has_kwargs: false
    -  description: Silero VAD (Voice Activity Detection) functions.
    -  default_handler: detect_voice
    +      name: diarize
       disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    -verbose: false
    +  default_handler: detect_voice
    +kind: job
     
             
         
    diff --git a/functions/master/silero_vad/latest/static/item.html b/functions/master/silero_vad/latest/static/item.html index a5696cbf..9e297535 100644 --- a/functions/master/silero_vad/latest/static/item.html +++ b/functions/master/silero_vad/latest/static/item.html @@ -31,7 +31,6 @@ apiVersion: v1 categories: - deep-learning -- pytorch - audio description: Silero VAD (Voice Activity Detection) functions. doc: '' @@ -43,7 +42,7 @@ author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.2 +mlrunVersion: 1.7.0 name: silero_vad platformVersion: 3.5.3 spec: @@ -57,7 +56,7 @@ - tqdm - onnxruntime url: '' -version: 1.3.0 +version: 1.4.0
    diff --git a/functions/master/silero_vad/latest/static/silero_vad.html b/functions/master/silero_vad/latest/static/silero_vad.html index 502167b2..feae13a6 100644 --- a/functions/master/silero_vad/latest/static/silero_vad.html +++ b/functions/master/silero_vad/latest/static/silero_vad.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/1.1.1/static/documentation.html b/functions/master/sklearn_classifier/1.1.1/static/documentation.html index 48056cda..1f3427a2 100644 --- a/functions/master/sklearn_classifier/1.1.1/static/documentation.html +++ b/functions/master/sklearn_classifier/1.1.1/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/1.1.1/static/example.html b/functions/master/sklearn_classifier/1.1.1/static/example.html index 9c4249a6..05d293d8 100644 --- a/functions/master/sklearn_classifier/1.1.1/static/example.html +++ b/functions/master/sklearn_classifier/1.1.1/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/1.1.1/static/sklearn_classifier.html b/functions/master/sklearn_classifier/1.1.1/static/sklearn_classifier.html index db33df83..e229f241 100644 --- a/functions/master/sklearn_classifier/1.1.1/static/sklearn_classifier.html +++ b/functions/master/sklearn_classifier/1.1.1/static/sklearn_classifier.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/latest/static/documentation.html b/functions/master/sklearn_classifier/latest/static/documentation.html index 48056cda..1f3427a2 100644 --- a/functions/master/sklearn_classifier/latest/static/documentation.html +++ b/functions/master/sklearn_classifier/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/latest/static/example.html b/functions/master/sklearn_classifier/latest/static/example.html index 9c4249a6..05d293d8 100644 --- a/functions/master/sklearn_classifier/latest/static/example.html +++ b/functions/master/sklearn_classifier/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier/latest/static/sklearn_classifier.html b/functions/master/sklearn_classifier/latest/static/sklearn_classifier.html index db33df83..e229f241 100644 --- a/functions/master/sklearn_classifier/latest/static/sklearn_classifier.html +++ b/functions/master/sklearn_classifier/latest/static/sklearn_classifier.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/1.1.1/static/documentation.html b/functions/master/sklearn_classifier_dask/1.1.1/static/documentation.html index d467f89f..38aa65c5 100644 --- a/functions/master/sklearn_classifier_dask/1.1.1/static/documentation.html +++ b/functions/master/sklearn_classifier_dask/1.1.1/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/1.1.1/static/example.html b/functions/master/sklearn_classifier_dask/1.1.1/static/example.html index 5fcdfa90..c37419fa 100644 --- a/functions/master/sklearn_classifier_dask/1.1.1/static/example.html +++ b/functions/master/sklearn_classifier_dask/1.1.1/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/1.1.1/static/sklearn_classifier_dask.html b/functions/master/sklearn_classifier_dask/1.1.1/static/sklearn_classifier_dask.html index 4a133c6c..9d03ccb2 100644 --- a/functions/master/sklearn_classifier_dask/1.1.1/static/sklearn_classifier_dask.html +++ b/functions/master/sklearn_classifier_dask/1.1.1/static/sklearn_classifier_dask.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/latest/static/documentation.html b/functions/master/sklearn_classifier_dask/latest/static/documentation.html index d467f89f..38aa65c5 100644 --- a/functions/master/sklearn_classifier_dask/latest/static/documentation.html +++ b/functions/master/sklearn_classifier_dask/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/latest/static/example.html b/functions/master/sklearn_classifier_dask/latest/static/example.html index 5fcdfa90..c37419fa 100644 --- a/functions/master/sklearn_classifier_dask/latest/static/example.html +++ b/functions/master/sklearn_classifier_dask/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/sklearn_classifier_dask/latest/static/sklearn_classifier_dask.html b/functions/master/sklearn_classifier_dask/latest/static/sklearn_classifier_dask.html index 4a133c6c..9d03ccb2 100644 --- a/functions/master/sklearn_classifier_dask/latest/static/sklearn_classifier_dask.html +++ b/functions/master/sklearn_classifier_dask/latest/static/sklearn_classifier_dask.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/structured_data_generator/1.6.0/src/function.yaml b/functions/master/structured_data_generator/1.6.0/src/function.yaml new file mode 100644 index 00000000..4e8a3562 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/src/function.yaml @@ -0,0 +1,56 @@ +spec: + build: + origin_filename: '' + requirements: + - langchain + - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo= + base_image: mlrun/mlrun + entry_points: + generate_data: + has_varargs: false + name: generate_data + has_kwargs: false + doc: 'Structured data of elements according to the given parameters. + + The data can be later logged as a structured file with MLRun''s `returns` + parameter.' + parameters: + - name: fields + type: list + doc: A list of fields to randomly generate. + - name: amount + type: int + doc: The number of variants to generate. + default: 10 + - name: model_name + type: str + doc: 'The name of the model to use for conversation generation. You should + choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models. + Default: ''gpt-3.5-turbo''.' + default: gpt-3.5-turbo + - name: language + type: str + doc: The language to use for the generated conversation text. + default: en + - name: chunk_size + type: int + doc: Number of samples generated at each GPT query. + default: 50 + outputs: + - type: list + lineno: 59 + command: '' + description: GenAI approach of generating structured data according to a given schema + default_handler: generate_data + disable_auto_mount: false + image: '' +metadata: + name: structured-data-generator + tag: '' + categories: + - data-generation + - genai +verbose: false +kind: job diff --git a/functions/master/structured_data_generator/1.6.0/src/item.yaml b/functions/master/structured_data_generator/1.6.0/src/item.yaml new file mode 100755 index 00000000..6e01aefb --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/src/item.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +categories: +- data-generation +- genai +description: GenAI approach of generating structured data according to a given schema +doc: '' +example: structured_data_generator.ipynb +generationDate: 2023-12-14:10-50 +hidden: false +icon: '' +labels: + author: zeevr +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.8.0 +name: structured_data_generator +platformVersion: 3.5.5 +spec: + filename: structured_data_generator.py + handler: generate_data + image: mlrun/mlrun + kind: job + requirements: + - langchain + - tqdm +url: '' +version: 1.6.0 diff --git a/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.ipynb b/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.ipynb new file mode 100644 index 00000000..12f87cf0 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9f7d79e7-8199-4680-919f-5039e8d7a0fe", + "metadata": {}, + "source": [ + "# structured_data_generator example" + ] + }, + { + "cell_type": "markdown", + "id": "4df1c846-2391-49a4-b65f-e7cff69dcdd9", + "metadata": {}, + "source": [ + "Introducing our innovative hub function, structured_data_generator, designed to streamline the process of creating structured files based on a list of fields.
    \n", + "This powerful function takes user-provided fields as input and dynamically generates relevant data, crafting a comprehensive structured file that aligns with the specified themes.
    \n", + "Whether you're working on content creation, testing scenarios, or simply need diverse data for development purposes, structured_data_generator is your go-to tool.
    " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3913a3b7-48c1-4b5a-8a28-8f2c93fc05d1", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import mlrun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "010c16e7-9d0a-42b1-9f09-141b72048885", + "metadata": {}, + "outputs": [], + "source": [ + "# OpenAI tokens:\n", + "OPENAI_API_KEY = \"\"\n", + "OPENAI_API_BASE = \"\"\n", + "os.environ[\"OPENAI_API_KEY\"] = OPENAI_API_KEY\n", + "os.environ[\"OPENAI_API_BASE\"] = OPENAI_API_BASE" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "324f2120-bcd9-4b61-a418-9c810709b6cf", + "metadata": {}, + "outputs": [], + "source": [ + "# Create mlrun project\n", + "project = mlrun.get_or_create_project(\"structured-data-generator-test\")\n", + "\n", + "# Import the function from the yaml file, once it's in the hub we can import from there \n", + "data_generation = project.set_function(func=\"./structured_data_generator.py\", name=\"structured_data_generator\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "999739d0-c8bf-48c3-8f57-b3c9ffec1a7f", + "metadata": {}, + "outputs": [], + "source": [ + "# Run the imported function with desired file/s and params\n", + "data_generation_run = data_generation.run(\n", + " handler=\"generate_data\",\n", + " params={\n", + " \"amount\": 5,\n", + " \"model_name\": \"gpt-4\",\n", + " \"language\": \"en\",\n", + " \"fields\": [\"first name\", \"last_name\", \"phone_number: at least 9 digits long\", \"email\", \"client_id: at least 8 digits long, only numbers\"],\n", + " },\n", + " returns=[\n", + " \"clients: file\",\n", + " ],\n", + " local=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dde97e2b-8570-4df4-84aa-04c341f455c9", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d70ceee-d17b-4901-9e8c-c9eda72f4e57", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3ea3341-80cc-4c87-a914-f2f3ffa1d491", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24983bf4-9fb0-4ebd-97cb-20e87859c22a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.py b/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.py new file mode 100644 index 00000000..34fa36d4 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/src/structured_data_generator.py @@ -0,0 +1,142 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import ast +import os + +import tqdm +from langchain.chat_models import ChatOpenAI + + +def _set_openai_secrets() -> bool: + key = "OPENAI_API_KEY" + base = "OPENAI_API_BASE" + # Check if the key is already in the environment variables: + if key in os.environ and base in os.environ: + return True + # Check if mlrun is installed: + try: + import mlrun + except ModuleNotFoundError: + raise EnvironmentError( + f"One or more of the OpenAI required environment variables ('{key}', '{base}') are missing." + f"Please set them as environment variables or install mlrun (`pip install mlrun`)" + f"and set them as project secrets using `projecy.set_secrets`." + ) + + # Check if the key is in the secrets: + context = mlrun.get_or_create_ctx(name="context") + openai_key = context.get_secret(key) + openai_base = context.get_secret(base) + + # If the key is not in the secrets, return False: + if not openai_key: + raise EnvironmentError( + f"Could not find OpenAI API key in the environment variables or secrets," + f" please set it as: {key}." + ) + if not openai_base: + raise EnvironmentError( + f"Could not find OpenAI API base in the environment variables or secrets," + f" please set it as: {base}." + ) + # If the key is in the secrets, set it in the environment variables and return True: + os.environ[key] = openai_key + os.environ[base] = openai_base + return True + + +def generate_data( + fields: list, + amount: int = 10, + model_name: str = "gpt-3.5-turbo", + language: str = "en", + chunk_size: int = 50, +) -> list: + """ + Structured data of elements according to the given parameters. + The data can be later logged as a structured file with MLRun's `returns` parameter. + + :param fields: A list of fields to randomly generate. + :param amount: The number of variants to generate. + :param model_name: The name of the model to use for conversation generation. + You should choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models. + Default: 'gpt-3.5-turbo'. + :param language: The language to use for the generated conversation text. + :param chunk_size: Number of samples generated at each GPT query. + """ + instructions = "" + for field in fields: + # Split the field to key and instruction: + if ":" in field: + key, instruction = field.split(":", 1) + else: + key, instruction = field, "no special instruction" + # Replace spaces with underscores for the key to be used as a json key: + key = key.strip().replace(" ", "_") + instructions += f"* {key}: {instruction}\n" + + # Create the prompt structure: + prompt_structure = ( + f"generate the following values {amount} times randomly, in an order that creates a json table.\n" + f"Use the following keys and instructions (example: 'key: instruction or no special instruction'): " + f"{instructions}.\n" + f"Please generate the values in {language} language. \n" + f"Make sure the names of the keys are the same as the given field name.\n" + f"Please return only the json format without any introduction and ending" + ) + + # Set the OpenAI secrets: + _set_openai_secrets() + + # Load the OpenAI model using langchain: + llm = ChatOpenAI(model=model_name) + + # Start generating data: + data = [] + for _ in tqdm.tqdm(range((amount // chunk_size) + 1), desc="Generating"): + # We try to generate the data 3 times, if we fail we raise an error: + for tryout in range(3): + # If the amount wanted is bigger than the chunk size, we generate a chunk of data in the size of the chunk + # and decrease the amount by the chunk size. + # otherwise we generate a chunk of data in the size of the amount: + if amount > chunk_size: + current_chunk_size = chunk_size + amount -= chunk_size + else: + current_chunk_size = amount + + # Create the prompt: + prompt = prompt_structure.format( + amount=current_chunk_size, + ) + + # Generate a chunk of data: + chunk_data = llm.predict(text=prompt) + + # Validate the response for correct python `list` structure + chunk_data = chunk_data[chunk_data.find("[") : chunk_data.rfind("]") + 1] + if chunk_data.count("[") != chunk_data.count("]"): + print( + "Failed to get proper json format from model, number of '[' doesn't match number of ']'." + ) + continue + chunk_data = ast.literal_eval(chunk_data) + data += chunk_data + break + if tryout == 3: + raise RuntimeError( + f"Could not generate a proper json format for the given fields, using given model: {model_name}." + f" Hint: Gpt-4 works best for most scenarios." + ) + return data diff --git a/functions/master/structured_data_generator/1.6.0/src/test_structured_data_generator.py b/functions/master/structured_data_generator/1.6.0/src/test_structured_data_generator.py new file mode 100644 index 00000000..3a7a7aa5 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/src/test_structured_data_generator.py @@ -0,0 +1,37 @@ +import os +import mlrun +import pytest + + +@pytest.mark.skipif("OPENAI_API_KEY" not in os.environ, reason="no token") +def test_structured_data_generator(): + # Create mlrun project + project = mlrun.get_or_create_project("structured-data-generator-test") + + #Set secrets + # project.set_secrets({"OPENAI_API_KEY": "", "OPENAI_API_BASE": ""}) + + # Import the function from the yaml file, once it's in the hub we can import from there + data_generation = project.set_function(func="structured_data_generator.py", name="structured_data_generator") + + # Run the imported function with desired file/s and params + data_generation_run = data_generation.run( + handler="generate_data", + params={ + "amount": 3, + "model_name": "gpt-4", + "language": "en", + "fields": [ + "first name", + "last_name", + "phone_number: at least 9 digits long", + "email", + "client_id: at least 8 digits long, only numbers" + ], + }, + returns=[ + "clients: file", + ], + local=True, + ) + assert data_generation_run.outputs["clients"] \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/documentation.html b/functions/master/structured_data_generator/1.6.0/static/documentation.html new file mode 100644 index 00000000..060d59d9 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/documentation.html @@ -0,0 +1,255 @@ + + + + + + + +structured_data_generator package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    structured_data_generator package

    + +
    + +
    +
    + +
    +
    +

    structured_data_generator package#

    +
    +

    Submodules#

    +
    +
    +

    structured_data_generator.structured_data_generator module#

    +
    +
    +structured_data_generator.structured_data_generator.generate_data(fields: list, amount: int = 10, model_name: str = 'gpt-3.5-turbo', language: str = 'en', chunk_size: int = 50) list[source]#
    +

    Structured data of elements according to the given parameters. +The data can be later logged as a structured file with MLRun’s returns parameter.

    +
    +
    Parameters:
    +
      +
    • fields – A list of fields to randomly generate.

    • +
    • amount – The number of variants to generate.

    • +
    • model_name – The name of the model to use for conversation generation. +You should choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models. +Default: ‘gpt-3.5-turbo’.

    • +
    • language – The language to use for the generated conversation text.

    • +
    • chunk_size – Number of samples generated at each GPT query.

    • +
    +
    +
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/example.html b/functions/master/structured_data_generator/1.6.0/static/example.html new file mode 100644 index 00000000..1573d754 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/example.html @@ -0,0 +1,248 @@ + + + + + + + +structured_data_generator example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +

    structured_data_generator example

    + +
    +
    +
    +
    +
    + +
    +
    +

    structured_data_generator example#

    +

    Introducing our innovative hub function, structured_data_generator, designed to streamline the process of creating structured files based on a list of fields.
    +This powerful function takes user-provided fields as input and dynamically generates relevant data, crafting a comprehensive structured file that aligns with the specified themes.
    +Whether you’re working on content creation, testing scenarios, or simply need diverse data for development purposes, structured_data_generator is your go-to tool.

    +
    +
    +
    import os
    +import mlrun
    +
    +
    +
    +
    +
    +
    +
    # OpenAI tokens:
    +OPENAI_API_KEY = ""
    +OPENAI_API_BASE = ""
    +os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
    +os.environ["OPENAI_API_BASE"] = OPENAI_API_BASE
    +
    +
    +
    +
    +
    +
    +
    # Create mlrun project
    +project = mlrun.get_or_create_project("structured-data-generator-test")
    +
    +# Import the function from the yaml file, once it's in the hub we can import from there 
    +data_generation = project.set_function(func="./structured_data_generator.py", name="structured_data_generator")
    +
    +
    +
    +
    +
    +
    +
    # Run the imported function with desired file/s and params
    +data_generation_run = data_generation.run(
    +    handler="generate_data",
    +            params={
    +                "amount": 5,
    +                "model_name": "gpt-4",
    +                "language": "en",
    +                "fields": ["first name", "last_name", "phone_number: at least 9 digits long", "email", "client_id: at least 8 digits long, only numbers"],
    +            },
    +            returns=[
    +                "clients: file",
    +            ],
    +    local=True,
    +)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/function.html b/functions/master/structured_data_generator/1.6.0/static/function.html new file mode 100644 index 00000000..11fdbe2f --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/function.html @@ -0,0 +1,91 @@ + + + + + + + + + + + Source + + + + +
    +        
    +spec:
    +  build:
    +    origin_filename: ''
    +    requirements:
    +    - langchain
    +    - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo=
    +    base_image: mlrun/mlrun
    +  entry_points:
    +    generate_data:
    +      has_varargs: false
    +      name: generate_data
    +      has_kwargs: false
    +      doc: 'Structured data of elements according to the given parameters.
    +
    +        The data can be later logged as a structured file with MLRun''s `returns`
    +        parameter.'
    +      parameters:
    +      - name: fields
    +        type: list
    +        doc: A list of fields to randomly generate.
    +      - name: amount
    +        type: int
    +        doc: The number of variants to generate.
    +        default: 10
    +      - name: model_name
    +        type: str
    +        doc: 'The name of the model to use for conversation generation. You should
    +          choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models.
    +          Default: ''gpt-3.5-turbo''.'
    +        default: gpt-3.5-turbo
    +      - name: language
    +        type: str
    +        doc: The language to use for the generated conversation text.
    +        default: en
    +      - name: chunk_size
    +        type: int
    +        doc: Number of samples generated at each GPT query.
    +        default: 50
    +      outputs:
    +      - type: list
    +      lineno: 59
    +  command: ''
    +  description: GenAI approach of generating structured data according to a given schema
    +  default_handler: generate_data
    +  disable_auto_mount: false
    +  image: ''
    +metadata:
    +  name: structured-data-generator
    +  tag: ''
    +  categories:
    +  - data-generation
    +  - genai
    +verbose: false
    +kind: job
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/item.html b/functions/master/structured_data_generator/1.6.0/static/item.html new file mode 100644 index 00000000..90c770e7 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/item.html @@ -0,0 +1,62 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- data-generation
    +- genai
    +description: GenAI approach of generating structured data according to a given schema
    +doc: ''
    +example: structured_data_generator.ipynb
    +generationDate: 2023-12-14:10-50
    +hidden: false
    +icon: ''
    +labels:
    +  author: zeevr
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.8.0
    +name: structured_data_generator
    +platformVersion: 3.5.5
    +spec:
    +  filename: structured_data_generator.py
    +  handler: generate_data
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +  - langchain
    +  - tqdm
    +url: ''
    +version: 1.6.0
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/source.html b/functions/master/structured_data_generator/1.6.0/static/source.html new file mode 100644 index 00000000..334b6e1e --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/source.html @@ -0,0 +1,177 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import ast
    +import os
    +
    +import tqdm
    +from langchain.chat_models import ChatOpenAI
    +
    +
    +def _set_openai_secrets() -> bool:
    +    key = "OPENAI_API_KEY"
    +    base = "OPENAI_API_BASE"
    +    # Check if the key is already in the environment variables:
    +    if key in os.environ and base in os.environ:
    +        return True
    +    # Check if mlrun is installed:
    +    try:
    +        import mlrun
    +    except ModuleNotFoundError:
    +        raise EnvironmentError(
    +            f"One or more of the OpenAI required environment variables ('{key}', '{base}') are missing."
    +            f"Please set them as environment variables or install mlrun (`pip install mlrun`)"
    +            f"and set them as project secrets using `projecy.set_secrets`."
    +        )
    +
    +    # Check if the key is in the secrets:
    +    context = mlrun.get_or_create_ctx(name="context")
    +    openai_key = context.get_secret(key)
    +    openai_base = context.get_secret(base)
    +
    +    # If the key is not in the secrets, return False:
    +    if not openai_key:
    +        raise EnvironmentError(
    +            f"Could not find OpenAI API key in the environment variables or secrets,"
    +            f" please set it as: {key}."
    +        )
    +    if not openai_base:
    +        raise EnvironmentError(
    +            f"Could not find OpenAI API base in the environment variables or secrets,"
    +            f" please set it as: {base}."
    +        )
    +    # If the key is in the secrets, set it in the environment variables and return True:
    +    os.environ[key] = openai_key
    +    os.environ[base] = openai_base
    +    return True
    +
    +
    +def generate_data(
    +    fields: list,
    +    amount: int = 10,
    +    model_name: str = "gpt-3.5-turbo",
    +    language: str = "en",
    +    chunk_size: int = 50,
    +) -> list:
    +    """
    +    Structured data of elements according to the given parameters.
    +    The data can be later logged as a structured file with MLRun's `returns` parameter.
    +
    +    :param fields: A list of fields to randomly generate.
    +    :param amount: The number of variants to generate.
    +    :param model_name: The name of the model to use for conversation generation.
    +                       You should choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models.
    +                       Default: 'gpt-3.5-turbo'.
    +    :param language: The language to use for the generated conversation text.
    +    :param chunk_size: Number of samples generated at each GPT query.
    +    """
    +    instructions = ""
    +    for field in fields:
    +        # Split the field to key and instruction:
    +        if ":" in field:
    +            key, instruction = field.split(":", 1)
    +        else:
    +            key, instruction = field, "no special instruction"
    +        # Replace spaces with underscores for the key to be used as a json key:
    +        key = key.strip().replace(" ", "_")
    +        instructions += f"* {key}: {instruction}\n"
    +
    +    # Create the prompt structure:
    +    prompt_structure = (
    +        f"generate the following values {amount} times randomly, in an order that creates a json table.\n"
    +        f"Use the following keys and instructions (example: 'key: instruction or no special instruction'): "
    +        f"{instructions}.\n"
    +        f"Please generate the values in {language} language. \n"
    +        f"Make sure the names of the keys are the same as the given field name.\n"
    +        f"Please return only the json format without any introduction and ending"
    +    )
    +
    +    # Set the OpenAI secrets:
    +    _set_openai_secrets()
    +
    +    # Load the OpenAI model using langchain:
    +    llm = ChatOpenAI(model=model_name)
    +
    +    # Start generating data:
    +    data = []
    +    for _ in tqdm.tqdm(range((amount // chunk_size) + 1), desc="Generating"):
    +        # We try to generate the data 3 times, if we fail we raise an error:
    +        for tryout in range(3):
    +            # If the amount wanted is bigger than the chunk size, we generate a chunk of data in the size of the chunk
    +            # and decrease the amount by the chunk size.
    +            # otherwise we generate a chunk of data in the size of the amount:
    +            if amount > chunk_size:
    +                current_chunk_size = chunk_size
    +                amount -= chunk_size
    +            else:
    +                current_chunk_size = amount
    +
    +            # Create the prompt:
    +            prompt = prompt_structure.format(
    +                amount=current_chunk_size,
    +            )
    +
    +            # Generate a chunk of data:
    +            chunk_data = llm.predict(text=prompt)
    +
    +            # Validate the response for correct python `list` structure
    +            chunk_data = chunk_data[chunk_data.find("[") : chunk_data.rfind("]") + 1]
    +            if chunk_data.count("[") != chunk_data.count("]"):
    +                print(
    +                    "Failed to get proper json format from model, number of '[' doesn't match number of ']'."
    +                )
    +                continue
    +            chunk_data = ast.literal_eval(chunk_data)
    +            data += chunk_data
    +            break
    +        if tryout == 3:
    +            raise RuntimeError(
    +                f"Could not generate a proper json format for the given fields, using given model: {model_name}."
    +                f" Hint: Gpt-4 works best for most scenarios."
    +            )
    +    return data
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/1.6.0/static/structured_data_generator.html b/functions/master/structured_data_generator/1.6.0/static/structured_data_generator.html new file mode 100644 index 00000000..b665f268 --- /dev/null +++ b/functions/master/structured_data_generator/1.6.0/static/structured_data_generator.html @@ -0,0 +1,317 @@ + + + + + + + +structured_data_generator.structured_data_generator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for structured_data_generator.structured_data_generator

    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import ast
    +import os
    +
    +import tqdm
    +from langchain.chat_models import ChatOpenAI
    +
    +
    +def _set_openai_secrets() -> bool:
    +    key = "OPENAI_API_KEY"
    +    base = "OPENAI_API_BASE"
    +    # Check if the key is already in the environment variables:
    +    if key in os.environ and base in os.environ:
    +        return True
    +    # Check if mlrun is installed:
    +    try:
    +        import mlrun
    +    except ModuleNotFoundError:
    +        raise EnvironmentError(
    +            f"One or more of the OpenAI required environment variables ('{key}', '{base}') are missing."
    +            f"Please set them as environment variables or install mlrun (`pip install mlrun`)"
    +            f"and set them as project secrets using `projecy.set_secrets`."
    +        )
    +
    +    # Check if the key is in the secrets:
    +    context = mlrun.get_or_create_ctx(name="context")
    +    openai_key = context.get_secret(key)
    +    openai_base = context.get_secret(base)
    +
    +    # If the key is not in the secrets, return False:
    +    if not openai_key:
    +        raise EnvironmentError(
    +            f"Could not find OpenAI API key in the environment variables or secrets,"
    +            f" please set it as: {key}."
    +        )
    +    if not openai_base:
    +        raise EnvironmentError(
    +            f"Could not find OpenAI API base in the environment variables or secrets,"
    +            f" please set it as: {base}."
    +        )
    +    # If the key is in the secrets, set it in the environment variables and return True:
    +    os.environ[key] = openai_key
    +    os.environ[base] = openai_base
    +    return True
    +
    +
    +
    +[docs] +def generate_data( + fields: list, + amount: int = 10, + model_name: str = "gpt-3.5-turbo", + language: str = "en", + chunk_size: int = 50, +) -> list: + """ + Structured data of elements according to the given parameters. + The data can be later logged as a structured file with MLRun's `returns` parameter. + + :param fields: A list of fields to randomly generate. + :param amount: The number of variants to generate. + :param model_name: The name of the model to use for conversation generation. + You should choose one of GPT-4 or GPT-3.5 from the list here: https://platform.openai.com/docs/models. + Default: 'gpt-3.5-turbo'. + :param language: The language to use for the generated conversation text. + :param chunk_size: Number of samples generated at each GPT query. + """ + instructions = "" + for field in fields: + # Split the field to key and instruction: + if ":" in field: + key, instruction = field.split(":", 1) + else: + key, instruction = field, "no special instruction" + # Replace spaces with underscores for the key to be used as a json key: + key = key.strip().replace(" ", "_") + instructions += f"* {key}: {instruction}\n" + + # Create the prompt structure: + prompt_structure = ( + f"generate the following values {amount} times randomly, in an order that creates a json table.\n" + f"Use the following keys and instructions (example: 'key: instruction or no special instruction'): " + f"{instructions}.\n" + f"Please generate the values in {language} language. \n" + f"Make sure the names of the keys are the same as the given field name.\n" + f"Please return only the json format without any introduction and ending" + ) + + # Set the OpenAI secrets: + _set_openai_secrets() + + # Load the OpenAI model using langchain: + llm = ChatOpenAI(model=model_name) + + # Start generating data: + data = [] + for _ in tqdm.tqdm(range((amount // chunk_size) + 1), desc="Generating"): + # We try to generate the data 3 times, if we fail we raise an error: + for tryout in range(3): + # If the amount wanted is bigger than the chunk size, we generate a chunk of data in the size of the chunk + # and decrease the amount by the chunk size. + # otherwise we generate a chunk of data in the size of the amount: + if amount > chunk_size: + current_chunk_size = chunk_size + amount -= chunk_size + else: + current_chunk_size = amount + + # Create the prompt: + prompt = prompt_structure.format( + amount=current_chunk_size, + ) + + # Generate a chunk of data: + chunk_data = llm.predict(text=prompt) + + # Validate the response for correct python `list` structure + chunk_data = chunk_data[chunk_data.find("[") : chunk_data.rfind("]") + 1] + if chunk_data.count("[") != chunk_data.count("]"): + print( + "Failed to get proper json format from model, number of '[' doesn't match number of ']'." + ) + continue + chunk_data = ast.literal_eval(chunk_data) + data += chunk_data + break + if tryout == 3: + raise RuntimeError( + f"Could not generate a proper json format for the given fields, using given model: {model_name}." + f" Hint: Gpt-4 works best for most scenarios." + ) + return data
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/structured_data_generator/latest/src/function.yaml b/functions/master/structured_data_generator/latest/src/function.yaml index 1093e178..4e8a3562 100644 --- a/functions/master/structured_data_generator/latest/src/function.yaml +++ b/functions/master/structured_data_generator/latest/src/function.yaml @@ -1,32 +1,17 @@ -kind: job -metadata: - name: structured-data-generator - tag: '' - hash: 44bb39f4bc55b38fc7ead1df24cb02bcf7f05bc9 - project: '' - labels: - author: zeevr - categories: - - machine-learning - - data-preparation - - data-generation - - genai spec: - command: '' - args: [] - image: '' build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo= - base_image: mlrun/mlrun - commands: [] - code_origin: '' origin_filename: '' requirements: - langchain - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo= + base_image: mlrun/mlrun entry_points: generate_data: + has_varargs: false name: generate_data + has_kwargs: false doc: 'Structured data of elements according to the given parameters. The data can be later logged as a structured file with MLRun''s `returns` @@ -56,16 +41,16 @@ spec: outputs: - type: list lineno: 59 - has_varargs: false - has_kwargs: false + command: '' description: GenAI approach of generating structured data according to a given schema default_handler: generate_data disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} + image: '' +metadata: + name: structured-data-generator + tag: '' + categories: + - data-generation + - genai verbose: false +kind: job diff --git a/functions/master/structured_data_generator/latest/src/item.yaml b/functions/master/structured_data_generator/latest/src/item.yaml index be2a2a94..6e01aefb 100755 --- a/functions/master/structured_data_generator/latest/src/item.yaml +++ b/functions/master/structured_data_generator/latest/src/item.yaml @@ -1,7 +1,5 @@ apiVersion: v1 categories: -- machine-learning -- data-preparation - data-generation - genai description: GenAI approach of generating structured data according to a given schema @@ -14,7 +12,7 @@ labels: author: zeevr maintainers: [] marketplaceType: '' -mlrunVersion: 1.6.1 +mlrunVersion: 1.8.0 name: structured_data_generator platformVersion: 3.5.5 spec: @@ -26,4 +24,4 @@ spec: - langchain - tqdm url: '' -version: 1.5.0 +version: 1.6.0 diff --git a/functions/master/structured_data_generator/latest/static/documentation.html b/functions/master/structured_data_generator/latest/static/documentation.html index 67e6b6d6..060d59d9 100644 --- a/functions/master/structured_data_generator/latest/static/documentation.html +++ b/functions/master/structured_data_generator/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/structured_data_generator/latest/static/example.html b/functions/master/structured_data_generator/latest/static/example.html index 589080f3..1573d754 100644 --- a/functions/master/structured_data_generator/latest/static/example.html +++ b/functions/master/structured_data_generator/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/structured_data_generator/latest/static/function.html b/functions/master/structured_data_generator/latest/static/function.html index e9070b88..11fdbe2f 100644 --- a/functions/master/structured_data_generator/latest/static/function.html +++ b/functions/master/structured_data_generator/latest/static/function.html @@ -28,35 +28,20 @@
             
    -kind: job
    -metadata:
    -  name: structured-data-generator
    -  tag: ''
    -  hash: 44bb39f4bc55b38fc7ead1df24cb02bcf7f05bc9
    -  project: ''
    -  labels:
    -    author: zeevr
    -  categories:
    -  - machine-learning
    -  - data-preparation
    -  - data-generation
    -  - genai
     spec:
    -  command: ''
    -  args: []
    -  image: ''
       build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo=
    -    base_image: mlrun/mlrun
    -    commands: []
    -    code_origin: ''
         origin_filename: ''
         requirements:
         - langchain
         - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgYXN0CmltcG9ydCBvcwoKaW1wb3J0IHRxZG0KZnJvbSBsYW5nY2hhaW4uY2hhdF9tb2RlbHMgaW1wb3J0IENoYXRPcGVuQUkKCgpkZWYgX3NldF9vcGVuYWlfc2VjcmV0cygpIC0+IGJvb2w6CiAgICBrZXkgPSAiT1BFTkFJX0FQSV9LRVkiCiAgICBiYXNlID0gIk9QRU5BSV9BUElfQkFTRSIKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICBpZiBrZXkgaW4gb3MuZW52aXJvbiBhbmQgYmFzZSBpbiBvcy5lbnZpcm9uOgogICAgICAgIHJldHVybiBUcnVlCiAgICAjIENoZWNrIGlmIG1scnVuIGlzIGluc3RhbGxlZDoKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgIGYiT25lIG9yIG1vcmUgb2YgdGhlIE9wZW5BSSByZXF1aXJlZCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgKCd7a2V5fScsICd7YmFzZX0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgIGYiUGxlYXNlIHNldCB0aGVtIGFzIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBpbnN0YWxsIG1scnVuIChgcGlwIGluc3RhbGwgbWxydW5gKSIKICAgICAgICAgICAgZiJhbmQgc2V0IHRoZW0gYXMgcHJvamVjdCBzZWNyZXRzIHVzaW5nIGBwcm9qZWN5LnNldF9zZWNyZXRzYC4iCiAgICAgICAgKQoKICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBpbiB0aGUgc2VjcmV0czoKICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJjb250ZXh0IikKICAgIG9wZW5haV9rZXkgPSBjb250ZXh0LmdldF9zZWNyZXQoa2V5KQogICAgb3BlbmFpX2Jhc2UgPSBjb250ZXh0LmdldF9zZWNyZXQoYmFzZSkKCiAgICAjIElmIHRoZSBrZXkgaXMgbm90IGluIHRoZSBzZWNyZXRzLCByZXR1cm4gRmFsc2U6CiAgICBpZiBub3Qgb3BlbmFpX2tleToKICAgICAgICByYWlzZSBFbnZpcm9ubWVudEVycm9yKAogICAgICAgICAgICBmIkNvdWxkIG5vdCBmaW5kIE9wZW5BSSBBUEkga2V5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgb3Igc2VjcmV0cywiCiAgICAgICAgICAgIGYiIHBsZWFzZSBzZXQgaXQgYXM6IHtrZXl9LiIKICAgICAgICApCiAgICBpZiBub3Qgb3BlbmFpX2Jhc2U6CiAgICAgICAgcmFpc2UgRW52aXJvbm1lbnRFcnJvcigKICAgICAgICAgICAgZiJDb3VsZCBub3QgZmluZCBPcGVuQUkgQVBJIGJhc2UgaW4gdGhlIGVudmlyb25tZW50IHZhcmlhYmxlcyBvciBzZWNyZXRzLCIKICAgICAgICAgICAgZiIgcGxlYXNlIHNldCBpdCBhczoge2Jhc2V9LiIKICAgICAgICApCiAgICAjIElmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHMsIHNldCBpdCBpbiB0aGUgZW52aXJvbm1lbnQgdmFyaWFibGVzIGFuZCByZXR1cm4gVHJ1ZToKICAgIG9zLmVudmlyb25ba2V5XSA9IG9wZW5haV9rZXkKICAgIG9zLmVudmlyb25bYmFzZV0gPSBvcGVuYWlfYmFzZQogICAgcmV0dXJuIFRydWUKCgpkZWYgZ2VuZXJhdGVfZGF0YSgKICAgIGZpZWxkczogbGlzdCwKICAgIGFtb3VudDogaW50ID0gMTAsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSAiZ3B0LTMuNS10dXJibyIsCiAgICBsYW5ndWFnZTogc3RyID0gImVuIiwKICAgIGNodW5rX3NpemU6IGludCA9IDUwLAopIC0+IGxpc3Q6CiAgICAiIiIKICAgIFN0cnVjdHVyZWQgZGF0YSBvZiBlbGVtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGdpdmVuIHBhcmFtZXRlcnMuCiAgICBUaGUgZGF0YSBjYW4gYmUgbGF0ZXIgbG9nZ2VkIGFzIGEgc3RydWN0dXJlZCBmaWxlIHdpdGggTUxSdW4ncyBgcmV0dXJuc2AgcGFyYW1ldGVyLgoKICAgIDpwYXJhbSBmaWVsZHM6IEEgbGlzdCBvZiBmaWVsZHMgdG8gcmFuZG9tbHkgZ2VuZXJhdGUuCiAgICA6cGFyYW0gYW1vdW50OiBUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIHRvIGdlbmVyYXRlLgogICAgOnBhcmFtIG1vZGVsX25hbWU6IFRoZSBuYW1lIG9mIHRoZSBtb2RlbCB0byB1c2UgZm9yIGNvbnZlcnNhdGlvbiBnZW5lcmF0aW9uLgogICAgICAgICAgICAgICAgICAgICAgIFlvdSBzaG91bGQgY2hvb3NlIG9uZSBvZiBHUFQtNCBvciBHUFQtMy41IGZyb20gdGhlIGxpc3QgaGVyZTogaHR0cHM6Ly9wbGF0Zm9ybS5vcGVuYWkuY29tL2RvY3MvbW9kZWxzLgogICAgICAgICAgICAgICAgICAgICAgIERlZmF1bHQ6ICdncHQtMy41LXR1cmJvJy4KICAgIDpwYXJhbSBsYW5ndWFnZTogVGhlIGxhbmd1YWdlIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRlZCBjb252ZXJzYXRpb24gdGV4dC4KICAgIDpwYXJhbSBjaHVua19zaXplOiBOdW1iZXIgb2Ygc2FtcGxlcyBnZW5lcmF0ZWQgYXQgZWFjaCBHUFQgcXVlcnkuCiAgICAiIiIKICAgIGluc3RydWN0aW9ucyA9ICIiCiAgICBmb3IgZmllbGQgaW4gZmllbGRzOgogICAgICAgICMgU3BsaXQgdGhlIGZpZWxkIHRvIGtleSBhbmQgaW5zdHJ1Y3Rpb246CiAgICAgICAgaWYgIjoiIGluIGZpZWxkOgogICAgICAgICAgICBrZXksIGluc3RydWN0aW9uID0gZmllbGQuc3BsaXQoIjoiLCAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGtleSwgaW5zdHJ1Y3Rpb24gPSBmaWVsZCwgIm5vIHNwZWNpYWwgaW5zdHJ1Y3Rpb24iCiAgICAgICAgIyBSZXBsYWNlIHNwYWNlcyB3aXRoIHVuZGVyc2NvcmVzIGZvciB0aGUga2V5IHRvIGJlIHVzZWQgYXMgYSBqc29uIGtleToKICAgICAgICBrZXkgPSBrZXkuc3RyaXAoKS5yZXBsYWNlKCIgIiwgIl8iKQogICAgICAgIGluc3RydWN0aW9ucyArPSBmIioge2tleX06IHtpbnN0cnVjdGlvbn1cbiIKCiAgICAjIENyZWF0ZSB0aGUgcHJvbXB0IHN0cnVjdHVyZToKICAgIHByb21wdF9zdHJ1Y3R1cmUgPSAoCiAgICAgICAgZiJnZW5lcmF0ZSB0aGUgZm9sbG93aW5nIHZhbHVlcyB7YW1vdW50fSB0aW1lcyByYW5kb21seSwgaW4gYW4gb3JkZXIgdGhhdCBjcmVhdGVzIGEganNvbiB0YWJsZS5cbiIKICAgICAgICBmIlVzZSB0aGUgZm9sbG93aW5nIGtleXMgYW5kIGluc3RydWN0aW9ucyAoZXhhbXBsZTogJ2tleTogaW5zdHJ1Y3Rpb24gb3Igbm8gc3BlY2lhbCBpbnN0cnVjdGlvbicpOiAiCiAgICAgICAgZiJ7aW5zdHJ1Y3Rpb25zfS5cbiIKICAgICAgICBmIlBsZWFzZSBnZW5lcmF0ZSB0aGUgdmFsdWVzIGluIHtsYW5ndWFnZX0gbGFuZ3VhZ2UuIFxuIgogICAgICAgIGYiTWFrZSBzdXJlIHRoZSBuYW1lcyBvZiB0aGUga2V5cyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdpdmVuIGZpZWxkIG5hbWUuXG4iCiAgICAgICAgZiJQbGVhc2UgcmV0dXJuIG9ubHkgdGhlIGpzb24gZm9ybWF0IHdpdGhvdXQgYW55IGludHJvZHVjdGlvbiBhbmQgZW5kaW5nIgogICAgKQoKICAgICMgU2V0IHRoZSBPcGVuQUkgc2VjcmV0czoKICAgIF9zZXRfb3BlbmFpX3NlY3JldHMoKQoKICAgICMgTG9hZCB0aGUgT3BlbkFJIG1vZGVsIHVzaW5nIGxhbmdjaGFpbjoKICAgIGxsbSA9IENoYXRPcGVuQUkobW9kZWw9bW9kZWxfbmFtZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgZGF0YToKICAgIGRhdGEgPSBbXQogICAgZm9yIF8gaW4gdHFkbS50cWRtKHJhbmdlKChhbW91bnQgLy8gY2h1bmtfc2l6ZSkgKyAxKSwgZGVzYz0iR2VuZXJhdGluZyIpOgogICAgICAgICMgV2UgdHJ5IHRvIGdlbmVyYXRlIHRoZSBkYXRhIDMgdGltZXMsIGlmIHdlIGZhaWwgd2UgcmFpc2UgYW4gZXJyb3I6CiAgICAgICAgZm9yIHRyeW91dCBpbiByYW5nZSgzKToKICAgICAgICAgICAgIyBJZiB0aGUgYW1vdW50IHdhbnRlZCBpcyBiaWdnZXIgdGhhbiB0aGUgY2h1bmsgc2l6ZSwgd2UgZ2VuZXJhdGUgYSBjaHVuayBvZiBkYXRhIGluIHRoZSBzaXplIG9mIHRoZSBjaHVuawogICAgICAgICAgICAjIGFuZCBkZWNyZWFzZSB0aGUgYW1vdW50IGJ5IHRoZSBjaHVuayBzaXplLgogICAgICAgICAgICAjIG90aGVyd2lzZSB3ZSBnZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGEgaW4gdGhlIHNpemUgb2YgdGhlIGFtb3VudDoKICAgICAgICAgICAgaWYgYW1vdW50ID4gY2h1bmtfc2l6ZToKICAgICAgICAgICAgICAgIGN1cnJlbnRfY2h1bmtfc2l6ZSA9IGNodW5rX3NpemUKICAgICAgICAgICAgICAgIGFtb3VudCAtPSBjaHVua19zaXplCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBjdXJyZW50X2NodW5rX3NpemUgPSBhbW91bnQKCiAgICAgICAgICAgICMgQ3JlYXRlIHRoZSBwcm9tcHQ6CiAgICAgICAgICAgIHByb21wdCA9IHByb21wdF9zdHJ1Y3R1cmUuZm9ybWF0KAogICAgICAgICAgICAgICAgYW1vdW50PWN1cnJlbnRfY2h1bmtfc2l6ZSwKICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhIGNodW5rIG9mIGRhdGE6CiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBsbG0ucHJlZGljdCh0ZXh0PXByb21wdCkKCiAgICAgICAgICAgICMgVmFsaWRhdGUgdGhlIHJlc3BvbnNlIGZvciBjb3JyZWN0IHB5dGhvbiBgbGlzdGAgc3RydWN0dXJlCiAgICAgICAgICAgIGNodW5rX2RhdGEgPSBjaHVua19kYXRhW2NodW5rX2RhdGEuZmluZCgiWyIpIDogY2h1bmtfZGF0YS5yZmluZCgiXSIpICsgMV0KICAgICAgICAgICAgaWYgY2h1bmtfZGF0YS5jb3VudCgiWyIpICE9IGNodW5rX2RhdGEuY291bnQoIl0iKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJGYWlsZWQgdG8gZ2V0IHByb3BlciBqc29uIGZvcm1hdCBmcm9tIG1vZGVsLCBudW1iZXIgb2YgJ1snIGRvZXNuJ3QgbWF0Y2ggbnVtYmVyIG9mICddJy4iCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBjaHVua19kYXRhID0gYXN0LmxpdGVyYWxfZXZhbChjaHVua19kYXRhKQogICAgICAgICAgICBkYXRhICs9IGNodW5rX2RhdGEKICAgICAgICAgICAgYnJlYWsKICAgICAgICBpZiB0cnlvdXQgPT0gMzoKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKAogICAgICAgICAgICAgICAgZiJDb3VsZCBub3QgZ2VuZXJhdGUgYSBwcm9wZXIganNvbiBmb3JtYXQgZm9yIHRoZSBnaXZlbiBmaWVsZHMsIHVzaW5nIGdpdmVuIG1vZGVsOiB7bW9kZWxfbmFtZX0uIgogICAgICAgICAgICAgICAgZiIgSGludDogR3B0LTQgd29ya3MgYmVzdCBmb3IgbW9zdCBzY2VuYXJpb3MuIgogICAgICAgICAgICApCiAgICByZXR1cm4gZGF0YQo=
    +    base_image: mlrun/mlrun
       entry_points:
         generate_data:
    +      has_varargs: false
           name: generate_data
    +      has_kwargs: false
           doc: 'Structured data of elements according to the given parameters.
     
             The data can be later logged as a structured file with MLRun''s `returns`
    @@ -86,19 +71,19 @@
           outputs:
           - type: list
           lineno: 59
    -      has_varargs: false
    -      has_kwargs: false
    +  command: ''
       description: GenAI approach of generating structured data according to a given schema
       default_handler: generate_data
       disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    +  image: ''
    +metadata:
    +  name: structured-data-generator
    +  tag: ''
    +  categories:
    +  - data-generation
    +  - genai
     verbose: false
    +kind: job
     
             
         
    diff --git a/functions/master/structured_data_generator/latest/static/item.html b/functions/master/structured_data_generator/latest/static/item.html index ffc6817d..90c770e7 100644 --- a/functions/master/structured_data_generator/latest/static/item.html +++ b/functions/master/structured_data_generator/latest/static/item.html @@ -30,8 +30,6 @@ apiVersion: v1 categories: -- machine-learning -- data-preparation - data-generation - genai description: GenAI approach of generating structured data according to a given schema @@ -44,7 +42,7 @@ author: zeevr maintainers: [] marketplaceType: '' -mlrunVersion: 1.6.1 +mlrunVersion: 1.8.0 name: structured_data_generator platformVersion: 3.5.5 spec: @@ -56,7 +54,7 @@ - langchain - tqdm url: '' -version: 1.5.0 +version: 1.6.0 diff --git a/functions/master/structured_data_generator/latest/static/structured_data_generator.html b/functions/master/structured_data_generator/latest/static/structured_data_generator.html index 51e05a22..b665f268 100644 --- a/functions/master/structured_data_generator/latest/static/structured_data_generator.html +++ b/functions/master/structured_data_generator/latest/static/structured_data_generator.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tags.json b/functions/master/tags.json index 04f007e8..52b80d8b 100644 --- a/functions/master/tags.json +++ b/functions/master/tags.json @@ -1 +1 @@ -{"kind": ["serving", "nuclio:serving", "job"], "categories": ["utils", "model-serving", "deep-learning", "huggingface", "etl", "machine-learning", "model-testing", "data-generation", "NLP", "data-preparation", "pytorch", "genai", "monitoring", "model-training", "data-analysis", "audio"]} \ No newline at end of file +{"categories": ["deep-learning", "data-generation", "audio", "NLP", "data-analysis", "model-testing", "monitoring", "data-preparation", "model-serving", "model-training", "machine-learning", "genai", "utils"], "kind": ["serving", "nuclio:serving", "job"]} \ No newline at end of file diff --git a/functions/master/test_classifier/1.1.0/static/documentation.html b/functions/master/test_classifier/1.1.0/static/documentation.html index e36e9403..ecb59d84 100644 --- a/functions/master/test_classifier/1.1.0/static/documentation.html +++ b/functions/master/test_classifier/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/test_classifier/1.1.0/static/example.html b/functions/master/test_classifier/1.1.0/static/example.html index 2d5bc2f0..7317badc 100644 --- a/functions/master/test_classifier/1.1.0/static/example.html +++ b/functions/master/test_classifier/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/test_classifier/1.1.0/static/test_classifier.html b/functions/master/test_classifier/1.1.0/static/test_classifier.html index 68421377..0ee946fa 100644 --- a/functions/master/test_classifier/1.1.0/static/test_classifier.html +++ b/functions/master/test_classifier/1.1.0/static/test_classifier.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/test_classifier/latest/static/documentation.html b/functions/master/test_classifier/latest/static/documentation.html index e36e9403..ecb59d84 100644 --- a/functions/master/test_classifier/latest/static/documentation.html +++ b/functions/master/test_classifier/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/test_classifier/latest/static/example.html b/functions/master/test_classifier/latest/static/example.html index 2d5bc2f0..7317badc 100644 --- a/functions/master/test_classifier/latest/static/example.html +++ b/functions/master/test_classifier/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/test_classifier/latest/static/test_classifier.html b/functions/master/test_classifier/latest/static/test_classifier.html index 68421377..0ee946fa 100644 --- a/functions/master/test_classifier/latest/static/test_classifier.html +++ b/functions/master/test_classifier/latest/static/test_classifier.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/1.3.0/src/function.yaml b/functions/master/text_to_audio_generator/1.3.0/src/function.yaml index f7fe5286..8edbde74 100644 --- a/functions/master/text_to_audio_generator/1.3.0/src/function.yaml +++ b/functions/master/text_to_audio_generator/1.3.0/src/function.yaml @@ -1,28 +1,8 @@ -metadata: - name: text-to-audio-generator - categories: - - data-preparation - - machine-learning - - pytorch - tag: '' spec: - command: '' - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg== - code_origin: '' - base_image: mlrun/mlrun - requirements: - - torchaudio - - pydub - origin_filename: '' - image: '' + default_handler: generate_multi_speakers_audio disable_auto_mount: false entry_points: generate_multi_speakers_audio: - has_kwargs: false - name: generate_multi_speakers_audio - doc: Generate audio files from text files. - has_varargs: false lineno: 38 parameters: - name: data_path @@ -89,11 +69,30 @@ spec: doc: Changes the bit depth for the supported formats. Supported only in "wav" or "flac" formats. default: null + name: generate_multi_speakers_audio + has_kwargs: false + has_varargs: false outputs: - doc: 'A tuple of: - The output directory path. - The generated audio files dataframe. - The errors'' dictionary.' type: Tuple[str, pd.DataFrame, dict] - default_handler: generate_multi_speakers_audio + doc: Generate audio files from text files. + command: '' + image: '' description: Generate audio file from text using different speakers -verbose: false + build: + requirements: + - torchaudio + - pydub + base_image: mlrun/mlrun + code_origin: '' + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg== +metadata: + categories: + - data-generation + - audio + tag: '' + name: text-to-audio-generator kind: job +verbose: false diff --git a/functions/master/text_to_audio_generator/1.3.0/src/item.yaml b/functions/master/text_to_audio_generator/1.3.0/src/item.yaml index e8235a08..3eba86ea 100644 --- a/functions/master/text_to_audio_generator/1.3.0/src/item.yaml +++ b/functions/master/text_to_audio_generator/1.3.0/src/item.yaml @@ -1,8 +1,7 @@ apiVersion: v1 categories: -- data-preparation -- machine-learning -- pytorch +- data-generation +- audio description: Generate audio file from text using different speakers doc: '' example: text_to_audio_generator.ipynb diff --git a/functions/master/text_to_audio_generator/1.3.0/static/documentation.html b/functions/master/text_to_audio_generator/1.3.0/static/documentation.html index e4ad7a88..b4660ebf 100644 --- a/functions/master/text_to_audio_generator/1.3.0/static/documentation.html +++ b/functions/master/text_to_audio_generator/1.3.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/1.3.0/static/example.html b/functions/master/text_to_audio_generator/1.3.0/static/example.html index 0b9f820f..096f4a9c 100644 --- a/functions/master/text_to_audio_generator/1.3.0/static/example.html +++ b/functions/master/text_to_audio_generator/1.3.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/1.3.0/static/function.html b/functions/master/text_to_audio_generator/1.3.0/static/function.html index 7e65f8e8..14516532 100644 --- a/functions/master/text_to_audio_generator/1.3.0/static/function.html +++ b/functions/master/text_to_audio_generator/1.3.0/static/function.html @@ -28,31 +28,11 @@
             
    -metadata:
    -  name: text-to-audio-generator
    -  categories:
    -  - data-preparation
    -  - machine-learning
    -  - pytorch
    -  tag: ''
     spec:
    -  command: ''
    -  build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg==
    -    code_origin: ''
    -    base_image: mlrun/mlrun
    -    requirements:
    -    - torchaudio
    -    - pydub
    -    origin_filename: ''
    -  image: ''
    +  default_handler: generate_multi_speakers_audio
       disable_auto_mount: false
       entry_points:
         generate_multi_speakers_audio:
    -      has_kwargs: false
    -      name: generate_multi_speakers_audio
    -      doc: Generate audio files from text files.
    -      has_varargs: false
           lineno: 38
           parameters:
           - name: data_path
    @@ -119,14 +99,33 @@
             doc: Changes the bit depth for the supported formats. Supported only in "wav"
               or "flac" formats.
             default: null
    +      name: generate_multi_speakers_audio
    +      has_kwargs: false
    +      has_varargs: false
           outputs:
           - doc: 'A tuple of: - The output directory path. - The generated audio files
               dataframe. - The errors'' dictionary.'
             type: Tuple[str, pd.DataFrame, dict]
    -  default_handler: generate_multi_speakers_audio
    +      doc: Generate audio files from text files.
    +  command: ''
    +  image: ''
       description: Generate audio file from text using different speakers
    -verbose: false
    +  build:
    +    requirements:
    +    - torchaudio
    +    - pydub
    +    base_image: mlrun/mlrun
    +    code_origin: ''
    +    origin_filename: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg==
    +metadata:
    +  categories:
    +  - data-generation
    +  - audio
    +  tag: ''
    +  name: text-to-audio-generator
     kind: job
    +verbose: false
     
             
         
    diff --git a/functions/master/text_to_audio_generator/1.3.0/static/item.html b/functions/master/text_to_audio_generator/1.3.0/static/item.html index 4fc64dfb..2aeea892 100644 --- a/functions/master/text_to_audio_generator/1.3.0/static/item.html +++ b/functions/master/text_to_audio_generator/1.3.0/static/item.html @@ -30,9 +30,8 @@ apiVersion: v1 categories: -- data-preparation -- machine-learning -- pytorch +- data-generation +- audio description: Generate audio file from text using different speakers doc: '' example: text_to_audio_generator.ipynb diff --git a/functions/master/text_to_audio_generator/1.3.0/static/text_to_audio_generator.html b/functions/master/text_to_audio_generator/1.3.0/static/text_to_audio_generator.html index c9c93a42..f807a73b 100644 --- a/functions/master/text_to_audio_generator/1.3.0/static/text_to_audio_generator.html +++ b/functions/master/text_to_audio_generator/1.3.0/static/text_to_audio_generator.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/latest/src/function.yaml b/functions/master/text_to_audio_generator/latest/src/function.yaml index f7fe5286..8edbde74 100644 --- a/functions/master/text_to_audio_generator/latest/src/function.yaml +++ b/functions/master/text_to_audio_generator/latest/src/function.yaml @@ -1,28 +1,8 @@ -metadata: - name: text-to-audio-generator - categories: - - data-preparation - - machine-learning - - pytorch - tag: '' spec: - command: '' - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg== - code_origin: '' - base_image: mlrun/mlrun - requirements: - - torchaudio - - pydub - origin_filename: '' - image: '' + default_handler: generate_multi_speakers_audio disable_auto_mount: false entry_points: generate_multi_speakers_audio: - has_kwargs: false - name: generate_multi_speakers_audio - doc: Generate audio files from text files. - has_varargs: false lineno: 38 parameters: - name: data_path @@ -89,11 +69,30 @@ spec: doc: Changes the bit depth for the supported formats. Supported only in "wav" or "flac" formats. default: null + name: generate_multi_speakers_audio + has_kwargs: false + has_varargs: false outputs: - doc: 'A tuple of: - The output directory path. - The generated audio files dataframe. - The errors'' dictionary.' type: Tuple[str, pd.DataFrame, dict] - default_handler: generate_multi_speakers_audio + doc: Generate audio files from text files. + command: '' + image: '' description: Generate audio file from text using different speakers -verbose: false + build: + requirements: + - torchaudio + - pydub + base_image: mlrun/mlrun + code_origin: '' + origin_filename: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg== +metadata: + categories: + - data-generation + - audio + tag: '' + name: text-to-audio-generator kind: job +verbose: false diff --git a/functions/master/text_to_audio_generator/latest/src/item.yaml b/functions/master/text_to_audio_generator/latest/src/item.yaml index e8235a08..3eba86ea 100644 --- a/functions/master/text_to_audio_generator/latest/src/item.yaml +++ b/functions/master/text_to_audio_generator/latest/src/item.yaml @@ -1,8 +1,7 @@ apiVersion: v1 categories: -- data-preparation -- machine-learning -- pytorch +- data-generation +- audio description: Generate audio file from text using different speakers doc: '' example: text_to_audio_generator.ipynb diff --git a/functions/master/text_to_audio_generator/latest/static/documentation.html b/functions/master/text_to_audio_generator/latest/static/documentation.html index e4ad7a88..b4660ebf 100644 --- a/functions/master/text_to_audio_generator/latest/static/documentation.html +++ b/functions/master/text_to_audio_generator/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/latest/static/example.html b/functions/master/text_to_audio_generator/latest/static/example.html index 0b9f820f..096f4a9c 100644 --- a/functions/master/text_to_audio_generator/latest/static/example.html +++ b/functions/master/text_to_audio_generator/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/text_to_audio_generator/latest/static/function.html b/functions/master/text_to_audio_generator/latest/static/function.html index 7e65f8e8..14516532 100644 --- a/functions/master/text_to_audio_generator/latest/static/function.html +++ b/functions/master/text_to_audio_generator/latest/static/function.html @@ -28,31 +28,11 @@
             
    -metadata:
    -  name: text-to-audio-generator
    -  categories:
    -  - data-preparation
    -  - machine-learning
    -  - pytorch
    -  tag: ''
     spec:
    -  command: ''
    -  build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg==
    -    code_origin: ''
    -    base_image: mlrun/mlrun
    -    requirements:
    -    - torchaudio
    -    - pydub
    -    origin_filename: ''
    -  image: ''
    +  default_handler: generate_multi_speakers_audio
       disable_auto_mount: false
       entry_points:
         generate_multi_speakers_audio:
    -      has_kwargs: false
    -      name: generate_multi_speakers_audio
    -      doc: Generate audio files from text files.
    -      has_varargs: false
           lineno: 38
           parameters:
           - name: data_path
    @@ -119,14 +99,33 @@
             doc: Changes the bit depth for the supported formats. Supported only in "wav"
               or "flac" formats.
             default: null
    +      name: generate_multi_speakers_audio
    +      has_kwargs: false
    +      has_varargs: false
           outputs:
           - doc: 'A tuple of: - The output directory path. - The generated audio files
               dataframe. - The errors'' dictionary.'
             type: Tuple[str, pd.DataFrame, dict]
    -  default_handler: generate_multi_speakers_audio
    +      doc: Generate audio files from text files.
    +  command: ''
    +  image: ''
       description: Generate audio file from text using different speakers
    -verbose: false
    +  build:
    +    requirements:
    +    - torchaudio
    +    - pydub
    +    base_image: mlrun/mlrun
    +    code_origin: ''
    +    origin_filename: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgaW1wb3J0bGliCmltcG9ydCBpbwppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3MKaW1wb3J0IHBhdGhsaWIKaW1wb3J0IHJhbmRvbQppbXBvcnQgdGVtcGZpbGUKZnJvbSBhYmMgaW1wb3J0IEFCQywgYWJzdHJhY3RtZXRob2QKZnJvbSB0eXBpbmcgaW1wb3J0IERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24KCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwppbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgpPUEVOQUlfQVBJX0tFWSA9ICJPUEVOQUlfQVBJX0tFWSIKT1BFTkFJX0JBU0VfVVJMID0gIk9QRU5BSV9BUElfQkFTRSIKU0FNUExFX1JBVEUgPSAyNDAwMAoKCmRlZiBnZW5lcmF0ZV9tdWx0aV9zcGVha2Vyc19hdWRpbygKICAgIGRhdGFfcGF0aDogc3RyLAogICAgc3BlYWtlcnM6IFVuaW9uW0xpc3Rbc3RyXSwgRGljdFtzdHIsIGludF1dLAogICAgYXZhaWxhYmxlX3ZvaWNlczogTGlzdFtzdHJdLAogICAgZW5naW5lOiBzdHIgPSAib3BlbmFpIiwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICB1c2VfZ3B1OiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICB1c2Vfc21hbGxfbW9kZWxzOiBPcHRpb25hbFtib29sXSA9IE5vbmUsCiAgICBvZmZsb2FkX2NwdTogT3B0aW9uYWxbYm9vbF0gPSBOb25lLAogICAgbW9kZWw6IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgc3BlZWQ6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUsCiAgICBzYW1wbGVfcmF0ZTogaW50ID0gMTYwMDAsCiAgICBmaWxlX2Zvcm1hdDogc3RyID0gIndhdiIsCiAgICB2ZXJib3NlOiBib29sID0gVHJ1ZSwKICAgIGJpdHNfcGVyX3NhbXBsZTogT3B0aW9uYWxbaW50XSA9IE5vbmUsCikgLT4gVHVwbGVbc3RyLCBwZC5EYXRhRnJhbWUsIGRpY3RdOgogICAgIiIiCiAgICBHZW5lcmF0ZSBhdWRpbyBmaWxlcyBmcm9tIHRleHQgZmlsZXMuCgogICAgOnBhcmFtIGRhdGFfcGF0aDogICAgICAgICAgIFBhdGggdG8gdGhlIHRleHQgZmlsZSBvciBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgdGV4dCBmaWxlcyB0byBnZW5lcmF0ZSBhdWRpbyBmcm9tLgogICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgICAgIExpc3QgLyBEaWN0IG9mIHNwZWFrZXJzIHRvIGdlbmVyYXRlIGF1ZGlvIGZvci4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBhIGxpc3QgaXMgZ2l2ZW4sIHRoZSBzcGVha2VycyB3aWxsIGJlIGFzc2lnbmVkIHRvIGNoYW5uZWxzIGluIHRoZSBvcmRlciBnaXZlbi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiBkaWN0aW9uYXJ5LCB0aGUga2V5cyB3aWxsIGJlIHRoZSBzcGVha2VycyBhbmQgdGhlIHZhbHVlcyB3aWxsIGJlIHRoZSBjaGFubmVscy4KICAgIDpwYXJhbSBhdmFpbGFibGVfdm9pY2VzOiAgICBMaXN0IG9mIGF2YWlsYWJsZSB2b2ljZXMgdG8gdXNlIGZvciB0aGUgZ2VuZXJhdGlvbi4KICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBiYXJrIGVuZ2luZToKICAgICAgICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdW5vLWFpLm5vdGlvbi5zaXRlLzhiOGU4NzQ5ZWQ1MTRiMGNiZjNmNjk5MDEzNTQ4NjgzP3Y9YmM2N2NmZjc4NmIwNGI1MGIzY2ViNzU2ZmQwNWY2OGMKICAgICAgICAgICAgICAgICAgICAgICAgU2VlIGhlcmUgZm9yIHRoZSBhdmFpbGFibGUgdm9pY2VzIGZvciBvcGVuYWkgZW5naW5lOgogICAgICAgICAgICAgICAgICAgICAgICBodHRwczovL2JldGEub3BlbmFpLmNvbS9kb2NzL2FwaS1yZWZlcmVuY2Uvc3BlZWNoCiAgICA6cGFyYW0gZW5naW5lOiAgICAgICAgICAgICAgVGhlIGVuZ2luZSB0byB1c2UgZm9yIHRoZSBnZW5lcmF0aW9uLiBTZWxlY3QgZWl0aGVyICJiYXJrIiBvciAib3BlbmFpIi4gRGVmYXVsdCBpcyAib3BlbmFpIi4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICBQYXRoIHRvIHRoZSBkaXJlY3RvcnkgdG8gc2F2ZSB0aGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIHRvLgogICAgOnBhcmFtIHVzZV9ncHU6ICAgICAgICAgICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBHUFUgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIHVzZV9zbWFsbF9tb2RlbHM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBzbWFsbCBtb2RlbHMgZm9yIHRoZSBnZW5lcmF0aW9uLiBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG9mZmxvYWRfY3B1OiAgICAgICAgIFRvIHJlZHVjZSB0aGUgbWVtb3J5IGZvb3RwcmludCwgdGhlIG1vZGVscyBjYW4gYmUgb2ZmbG9hZGVkIHRvIHRoZSBDUFUgYWZ0ZXIgbG9hZGluZy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAiYmFyayIgZW5naW5lLgogICAgOnBhcmFtIG1vZGVsOiAgICAgICAgICAgICAgIFdoaWNoIG1vZGVsIHRvIHVzZSBmb3IgdGhlIGdlbmVyYXRpb24uIFN1cHBvcnRlZCBvbmx5IGluICJvcGVuYWkiIGVuZ2luZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWZhdWx0IGlzICJ0dHMtMSIuCiAgICA6cGFyYW0gc3BlZWQ6ICAgICAgICAgICAgICAgVGhlIHNwZWVkIG9mIHRoZSBnZW5lcmF0ZWQgYXVkaW8uIFNlbGVjdCBhIHZhbHVlIGZyb20gYDAuMjVgIHRvIGA0LjBgLiBgMS4wYCBpcyB0aGUgZGVmYXVsdC4KICAgIDpwYXJhbSBzYW1wbGVfcmF0ZTogICAgICAgICBUaGUgc2FtcGxpbmcgcmF0ZSBvZiB0aGUgZ2VuZXJhdGVkIGF1ZGlvLgogICAgOnBhcmFtIGZpbGVfZm9ybWF0OiAgICAgICAgIFRoZSBmb3JtYXQgb2YgdGhlIGdlbmVyYXRlZCBhdWRpbyBmaWxlcy4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgZ2VuZXJhdGlvbi4KICAgIDpwYXJhbSBiaXRzX3Blcl9zYW1wbGU6ICAgICBDaGFuZ2VzIHRoZSBiaXQgZGVwdGggZm9yIHRoZSBzdXBwb3J0ZWQgZm9ybWF0cy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTdXBwb3J0ZWQgb25seSBpbiAid2F2IiBvciAiZmxhYyIgZm9ybWF0cy4KCiAgICA6cmV0dXJuczogICAgICAgICAgICAgICAgICAgQSB0dXBsZSBvZjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHBhdGguCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBUaGUgZ2VuZXJhdGVkIGF1ZGlvIGZpbGVzIGRhdGFmcmFtZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIFRoZSBlcnJvcnMnIGRpY3Rpb25hcnkuCiAgICAiIiIKCiAgICBnbG9iYWwgX0xPR0dFUgogICAgX0xPR0dFUiA9IF9nZXRfbG9nZ2VyKCkKICAgICMgR2V0IHRoZSBpbnB1dCB0ZXh0IGZpbGVzIHRvIHR1cm4gdG8gYXVkaW86CiAgICBkYXRhX3BhdGggPSBwYXRobGliLlBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCiAgICB0ZXh0X2ZpbGVzID0gX2dldF90ZXh0X2ZpbGVzKGRhdGFfcGF0aD1kYXRhX3BhdGgpCgoKICAgICMgUHJlcGFyZSB0aGUgc3BlZWNoIGVuZ2luZToKICAgIGVuZ2luZSA9IF9nZXRfZW5naW5lKAogICAgICAgIGVuZ2luZT1lbmdpbmUsCiAgICAgICAgdXNlX2dwdT11c2VfZ3B1LAogICAgICAgIHVzZV9zbWFsbF9tb2RlbHM9dXNlX3NtYWxsX21vZGVscywKICAgICAgICBvZmZsb2FkX2NwdT1vZmZsb2FkX2NwdSwKICAgICAgICBtb2RlbD1tb2RlbCwKICAgICAgICBmaWxlX2Zvcm1hdD1maWxlX2Zvcm1hdCwKICAgICAgICBzcGVlZD1zcGVlZAogICAgKQoKICAgICMgQ2hlY2sgZm9yIHBlciBjaGFubmVsIGdlbmVyYXRpb246CiAgICBpZiBpc2luc3RhbmNlKHNwZWFrZXJzLCBkaWN0KToKICAgICAgICBzcGVha2VyX3Blcl9jaGFubmVsID0gVHJ1ZQogICAgICAgICMgU29ydCB0aGUgZ2l2ZW4gc3BlYWtlcnMgYnkgY2hhbm5lbHM6CiAgICAgICAgc3BlYWtlcnMgPSB7CiAgICAgICAgICAgIHNwZWFrZXI6IGNoYW5uZWwKICAgICAgICAgICAgZm9yIHNwZWFrZXIsIGNoYW5uZWwgaW4gc29ydGVkKHNwZWFrZXJzLml0ZW1zKCksIGtleT1sYW1iZGEgaXRlbTogaXRlbVsxXSkKICAgICAgICB9CiAgICBlbHNlOgogICAgICAgIHNwZWFrZXJfcGVyX2NoYW5uZWwgPSBGYWxzZQoKICAgICMgUHJlcGFyZSB0aGUgcmVzYW1wbGluZyBtb2R1bGU6CiAgICByZXNhbXBsZXIgPSB0b3JjaGF1ZGlvLnRyYW5zZm9ybXMuUmVzYW1wbGUoCiAgICAgICAgb3JpZ19mcmVxPVNBTVBMRV9SQVRFLCBuZXdfZnJlcT1zYW1wbGVfcmF0ZSwgZHR5cGU9dG9yY2guZmxvYXQzMgogICAgKQoKICAgICMgUHJlcGFyZSB0aGUgZ2FwIGJldHdlZW4gZWFjaCBzcGVha2VyOgogICAgZ2FwX2JldHdlZW5fc3BlYWtlcnMgPSBucC56ZXJvcyhpbnQoMC41ICogU0FNUExFX1JBVEUpKQoKICAgICMgUHJlcGFyZSB0aGUgc3VjY2Vzc2VzIGRhdGFmcmFtZSBhbmQgZXJyb3JzIGRpY3Rpb25hcnkgdG8gYmUgcmV0dXJuZWQ6CiAgICBzdWNjZXNzZXMgPSBbXQogICAgZXJyb3JzID0ge30KCiAgICAjIENyZWF0ZSB0aGUgb3V0cHV0IGRpcmVjdG9yeToKICAgIGlmIG91dHB1dF9kaXJlY3RvcnkgaXMgTm9uZToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gdGVtcGZpbGUubWtkdGVtcCgpCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gcGF0aGxpYi5QYXRoKG91dHB1dF9kaXJlY3RvcnkpCiAgICBpZiBub3Qgb3V0cHV0X2RpcmVjdG9yeS5leGlzdHMoKToKICAgICAgICBvdXRwdXRfZGlyZWN0b3J5Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAjIFN0YXJ0IGdlbmVyYXRpbmcgYXVkaW86CiAgICAjIEdvIG92ZXIgdGhlIGF1ZGlvIGZpbGVzIGFuZCB0cmFuc2NyaWJlOgogICAgZm9yIHRleHRfZmlsZSBpbiB0cWRtLnRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iR2VuZXJhdGluZyIsIHVuaXQ9ImZpbGUiLCBkaXNhYmxlPW5vdCB2ZXJib3NlCiAgICApOgoKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgUmFuZG9taXplIHZvaWNlcyBmb3IgZWFjaCBzcGVha2VyOgogICAgICAgICAgICBjaG9zZW5fdm9pY2VzID0ge30KICAgICAgICAgICAgYXZhaWxhYmxlX3ZvaWNlc19jb3B5ID0gYXZhaWxhYmxlX3ZvaWNlcy5jb3B5KCkKICAgICAgICAgICAgZm9yIHNwZWFrZXIgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICB2b2ljZSA9IHJhbmRvbS5jaG9pY2UoYXZhaWxhYmxlX3ZvaWNlc19jb3B5KQogICAgICAgICAgICAgICAgY2hvc2VuX3ZvaWNlc1tzcGVha2VyXSA9IHZvaWNlCiAgICAgICAgICAgICAgICBhdmFpbGFibGVfdm9pY2VzX2NvcHkucmVtb3ZlKHZvaWNlKQogICAgICAgICAgICAjIFJlYWQgdGV4dDoKICAgICAgICAgICAgd2l0aCBvcGVuKHRleHRfZmlsZSwgInIiKSBhcyBmcDoKICAgICAgICAgICAgICAgIHRleHQgPSBmcC5yZWFkKCkKICAgICAgICAgICAgIyBQcmVwYXJlIGEgaG9sZGVyIGZvciBhbGwgdGhlIGdlbmVyYXRlZCBwaWVjZXMgKGlmIHBlciBjaGFubmVsIGVhY2ggc3BlYWtlciB3aWxsIGhhdmUgaXRzIG93bik6CiAgICAgICAgICAgIGF1ZGlvX3BpZWNlcyA9ICgKICAgICAgICAgICAgICAgIHtzcGVha2VyOiBbXSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc30KICAgICAgICAgICAgICAgIGlmIHNwZWFrZXJfcGVyX2NoYW5uZWwKICAgICAgICAgICAgICAgIGVsc2UgeyJhbGwiOiBbXX0KICAgICAgICAgICAgKQoKICAgICAgICAgICAgIyBHZW5lcmF0ZSBhdWRpbyBwZXIgbGluZToKICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdGxpbmVzKCk6CiAgICAgICAgICAgICAgICAjIFZhbGlkYXRlIGxpbmUgaXMgaW4gY29ycmVjdCBzcGVha2VyIGZvcm1hdDoKCiAgICAgICAgICAgICAgICBpZiAiOiAiIG5vdCBpbiBsaW5lOgogICAgICAgICAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIlNraXBwaW5nIGxpbmU6IHtsaW5lfSIpCiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICMgU3BsaXQgbGluZSB0byBzcGVha2VyIGFuZCBoaXMgd29yZHM6CiAgICAgICAgICAgICAgICBjdXJyZW50X3NwZWFrZXIsIHNlbnRlbmNlcyA9IGxpbmUuc3BsaXQoIjogIiwgMSkKICAgICAgICAgICAgICAgICMgVmFsaWRhdGUgc3BlYWtlciBpcyBrbm93bjoKICAgICAgICAgICAgICAgIGlmIGN1cnJlbnRfc3BlYWtlciBub3QgaW4gc3BlYWtlcnM6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJVbmtub3duIHNwZWFrZXI6IHtjdXJyZW50X3NwZWFrZXJ9LiBHaXZlbiBzcGVha2VycyBhcmU6IHtzcGVha2Vyc30iCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIHNlbnRlbmNlIGluIF9zcGxpdF9saW5lKGxpbmU9c2VudGVuY2VzKToKICAgICAgICAgICAgICAgICAgICAjIEdlbmVyYXRlIHdvcmRzIGF1ZGlvOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvID0gZW5naW5lLl9nZW5lcmF0ZV9hdWRpbygKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dD1zZW50ZW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgdm9pY2U9Y2hvc2VuX3ZvaWNlc1tjdXJyZW50X3NwZWFrZXJdLAogICAgICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgICAgICAgICAgaWYgc3BlYWtlcl9wZXJfY2hhbm5lbDoKICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW5jZSA9IG5wLnplcm9zX2xpa2UoYXVkaW8pCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBzcGVha2VyIGluIGF1ZGlvX3BpZWNlcy5rZXlzKCk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBzcGVha2VyID09IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbc3BlYWtlcl0gKz0gW2F1ZGlvLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXVkaW9fcGllY2VzW3NwZWFrZXJdICs9IFtzaWxlbmNlLCBnYXBfYmV0d2Vlbl9zcGVha2Vyc10KICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBhdWRpb19waWVjZXNbImFsbCJdICs9IFthdWRpbywgZ2FwX2JldHdlZW5fc3BlYWtlcnNdCiAgICAgICAgICAgICMgQ29uc3RydWN0IGEgc2luZ2xlIGF1ZGlvIGFycmF5IGZyb20gYWxsIHRoZSBwaWVjZXMgYW5kIGNoYW5uZWxzOgoKICAgICAgICAgICAgYXVkaW8gPSBucC52c3RhY2soCiAgICAgICAgICAgICAgICBbbnAuY29uY2F0ZW5hdGUoYXVkaW9fcGllY2VzW3NwZWFrZXJdKSBmb3Igc3BlYWtlciBpbiBzcGVha2Vyc10KICAgICAgICAgICAgKS5hc3R5cGUoZHR5cGU9bnAuZmxvYXQzMikKICAgICAgICAgICAgIyBSZXNhbXBsZToKICAgICAgICAgICAgYXVkaW8gPSB0b3JjaC5mcm9tX251bXB5KGF1ZGlvKQogICAgICAgICAgICBhdWRpbyA9IHJlc2FtcGxlcihhdWRpbykKICAgICAgICAgICAgIyBTYXZlIHRvIGF1ZGlvIGZpbGU6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7dGV4dF9maWxlLnN0ZW19LntmaWxlX2Zvcm1hdH0iCgogICAgICAgICAgICB0b3JjaGF1ZGlvLnNhdmUoCiAgICAgICAgICAgICAgICB1cmk9c3RyKGF1ZGlvX2ZpbGUpLAogICAgICAgICAgICAgICAgc3JjPWF1ZGlvLAogICAgICAgICAgICAgICAgc2FtcGxlX3JhdGU9c2FtcGxlX3JhdGUsCiAgICAgICAgICAgICAgICBmb3JtYXQ9ZmlsZV9mb3JtYXQsCiAgICAgICAgICAgICAgICBiaXRzX3Blcl9zYW1wbGU9Yml0c19wZXJfc2FtcGxlLAogICAgICAgICAgICApCgogICAgICAgICAgICAjIENvbGxlY3QgdG8gdGhlIHN1Y2Nlc3NlczoKICAgICAgICAgICAgc3VjY2Vzc2VzLmFwcGVuZChbdGV4dF9maWxlLm5hbWUsIGF1ZGlvX2ZpbGUubmFtZV0pCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGNlcHRpb246CiAgICAgICAgICAgICMgTm90ZSB0aGUgZXhjZXB0aW9uIGFzIGVycm9yIGluIHRoZSBkaWN0aW9uYXJ5OgogICAgICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICAgICAgX0xPR0dFUi53YXJuaW5nKGYiRXJyb3IgaW4gZmlsZTogJ3t0ZXh0X2ZpbGUubmFtZX0nIikKICAgICAgICAgICAgcHJpbnQoZXhjZXB0aW9uKQogICAgICAgICAgICBlcnJvcnNbdGV4dF9maWxlLm5hbWVdID0gc3RyKGV4Y2VwdGlvbikKCiAgICAjIENvbnN0cnVjdCB0aGUgdHJhbnNsYXRpb25zIGRhdGFmcmFtZToKICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1bInRleHRfZmlsZSIsICJhdWRpb19maWxlIl0sCiAgICApCgogICAgIyBQcmludCB0aGUgaGVhZCBvZiB0aGUgcHJvZHVjZWQgZGF0YWZyYW1lIGFuZCByZXR1cm46CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKHRleHRfZmlsZXMpfSlcbiIKICAgICAgICAgICAgZiJUcmFuc2xhdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQogICAgcmV0dXJuIHN0cihvdXRwdXRfZGlyZWN0b3J5KSwgc3VjY2Vzc2VzLCBlcnJvcnMKCgpjbGFzcyBTcGVlY2hFbmdpbmUoQUJDKToKICAgIEBhYnN0cmFjdG1ldGhvZAogICAgZGVmIF9nZW5lcmF0ZV9hdWRpbyhzZWxmLCB0ZXh0OiBzdHIsIHZvaWNlOiBzdHIpIC0+IG5wLm5kYXJyYXk6CiAgICAgICAgcGFzcwoKCmNsYXNzIEJhcmtFbmdpbmUoU3BlZWNoRW5naW5lKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCB1c2VfZ3B1OiBib29sID0gVHJ1ZSwgdXNlX3NtYWxsX21vZGVsczogYm9vbCA9IEZhbHNlLCBvZmZsb2FkX2NwdTogYm9vbCA9IEZhbHNlKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuYmFyayA9IGltcG9ydGxpYi5pbXBvcnRfbW9kdWxlKCJiYXJrIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHJhaXNlIEltcG9ydEVycm9yKAogICAgICAgICAgICAgICAgIlRoZSAnYmFyaycgbGlicmFyeSBpcyByZXF1aXJlZCBmb3IgdGhlIEJhcmtFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIGl0IHVzaW5nICdwaXAgaW5zdGFsbCBiYXJrLWFpJy4iCiAgICAgICAgICAgICkKCiAgICAgICAgc2VsZi5iYXJrLnByZWxvYWRfbW9kZWxzKAogICAgICAgICAgICB0ZXh0X3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgdGV4dF91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29hcnNlX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgY29hcnNlX3VzZV9zbWFsbD11c2Vfc21hbGxfbW9kZWxzLAogICAgICAgICAgICBmaW5lX3VzZV9ncHU9dXNlX2dwdSwKICAgICAgICAgICAgZmluZV91c2Vfc21hbGw9dXNlX3NtYWxsX21vZGVscywKICAgICAgICAgICAgY29kZWNfdXNlX2dwdT11c2VfZ3B1LAogICAgICAgICAgICBmb3JjZV9yZWxvYWQ9b2ZmbG9hZF9jcHUsCiAgICAgICAgKQoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmJhcmsuZ2VuZXJhdGVfYXVkaW8oCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIGhpc3RvcnlfcHJvbXB0PXZvaWNlLAogICAgICAgICAgICBzaWxlbnQ9VHJ1ZSwKICAgICAgICApCiAgICAgICAgcmV0dXJuIGF1ZGlvCgoKY2xhc3MgT3BlbkFJRW5naW5lKFNwZWVjaEVuZ2luZSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbW9kZWw6IHN0ciA9ICJ0dHMtMSIsIGZpbGVfZm9ybWF0OiBzdHIgPSAid2F2Iiwgc3BlZWQ6IGZsb2F0ID0gMS4wKToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYub3BlbmFpID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoIm9wZW5haSIpCiAgICAgICAgICAgIHNlbGYucHlkdWIgPSBpbXBvcnRsaWIuaW1wb3J0X21vZHVsZSgicHlkdWIiKQogICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgcmFpc2UgSW1wb3J0RXJyb3IoCiAgICAgICAgICAgICAgICAiVGhlICdvcGVuYWknIGFuZCAncHlkdWInIGxpYnJhcmllcyBhcmUgcmVxdWlyZWQgZm9yIHRoZSBPcGVuQUlFbmdpbmUuIFBsZWFzZSBpbnN0YWxsIHRoZW0gdXNpbmcgJ3BpcCBpbnN0YWxsIG9wZW5haSBweWR1YicuIgogICAgICAgICAgICApCgogICAgICAgIGFwaV9rZXkgPSBvcy5nZXRlbnYoT1BFTkFJX0FQSV9LRVkpCiAgICAgICAgYmFzZV91cmwgPSBvcy5nZXRlbnYoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICMgQ2hlY2sgaWYgdGhlIGtleSBpcyBhbHJlYWR5IGluIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZXM6CiAgICAgICAgaWYgbm90IGFwaV9rZXkgb3Igbm90IGJhc2VfdXJsOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgICAgICAgICBjb250ZXh0ID0gbWxydW4uZ2V0X29yX2NyZWF0ZV9jdHgobmFtZT0iY29udGV4dCIpCiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBrZXkgaXMgaW4gdGhlIHNlY3JldHM6CiAgICAgICAgICAgICAgICBhcGlfa2V5ID0gY29udGV4dC5nZXRfc2VjcmV0KE9QRU5BSV9BUElfS0VZKQogICAgICAgICAgICAgICAgYmFzZV91cmwgPSBjb250ZXh0LmdldF9zZWNyZXQoT1BFTkFJX0JBU0VfVVJMKQogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICAgICAgICAgIHJhaXNlIEVudmlyb25tZW50RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgZiJPbmUgb3IgbW9yZSBvZiB0aGUgT3BlbkFJIHJlcXVpcmVkIGVudmlyb25tZW50IHZhcmlhYmxlcyAoJ3tPUEVOQUlfQVBJX0tFWX0nLCAne09QRU5BSV9CQVNFX1VSTH0nKSBhcmUgbWlzc2luZy4iCiAgICAgICAgICAgICAgICAgICAgZiJQbGVhc2Ugc2V0IHRoZW0gYXMgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIGluc3RhbGwgbWxydW4gKGBwaXAgaW5zdGFsbCBtbHJ1bmApIgogICAgICAgICAgICAgICAgICAgIGYiYW5kIHNldCB0aGVtIGFzIHByb2plY3Qgc2VjcmV0cyB1c2luZyBgcHJvamVjdC5zZXRfc2VjcmV0c2AuIgogICAgICAgICAgICAgICAgKQoKICAgICAgICBzZWxmLmNsaWVudCA9IHNlbGYub3BlbmFpLk9wZW5BSShhcGlfa2V5PWFwaV9rZXksIGJhc2VfdXJsPWJhc2VfdXJsKQogICAgICAgIHNlbGYubW9kZWwgPSBtb2RlbAogICAgICAgIHNlbGYuZmlsZV9mb3JtYXQgPSBmaWxlX2Zvcm1hdAogICAgICAgIHNlbGYuc3BlZWQgPSBzcGVlZAoKICAgIGRlZiBfZ2VuZXJhdGVfYXVkaW8oc2VsZiwgdGV4dDogc3RyLCB2b2ljZTogc3RyKSAtPiBucC5uZGFycmF5OgogICAgICAgICMgR2VuZXJhdGUgd29yZHMgYXVkaW86CiAgICAgICAgYXVkaW8gPSBzZWxmLmNsaWVudC5hdWRpby5zcGVlY2guY3JlYXRlKAogICAgICAgICAgICBtb2RlbD1zZWxmLm1vZGVsLAogICAgICAgICAgICBpbnB1dD10ZXh0LAogICAgICAgICAgICB2b2ljZT12b2ljZSwKICAgICAgICAgICAgcmVzcG9uc2VfZm9ybWF0PXNlbGYuZmlsZV9mb3JtYXQsCiAgICAgICAgICAgIHNwZWVkPXNlbGYuc3BlZWQsCiAgICAgICAgKQogICAgICAgIGF1ZGlvID0gYXVkaW8uY29udGVudAogICAgICAgIGF1ZGlvID0gc2VsZi5fYnl0ZXNfdG9fbnBfYXJyYXkoYXVkaW89YXVkaW8pCiAgICAgICAgcmV0dXJuIGF1ZGlvCgogICAgZGVmIF9ieXRlc190b19ucF9hcnJheShzZWxmLCBhdWRpbzogYnl0ZXMpOgogICAgICAgIGlmIHNlbGYuZmlsZV9mb3JtYXQgPT0gIm1wMyI6CiAgICAgICAgICAgIGF1ZGlvX3NlZ21lbnQgPSBzZWxmLnB5ZHViLkF1ZGlvU2VnbWVudC5mcm9tX21wMyhpby5CeXRlc0lPKGF1ZGlvKSkKCiAgICAgICAgICAgICMgQ29udmVydCB0byByYXcgUENNIGF1ZGlvIGRhdGEKICAgICAgICAgICAgc2FtcGxlcyA9IGF1ZGlvX3NlZ21lbnQuZ2V0X2FycmF5X29mX3NhbXBsZXMoKQoKICAgICAgICAgICAgIyBDb252ZXJ0IHRvIG51bXB5IGFycmF5CiAgICAgICAgICAgIGF1ZGlvX2FycmF5ID0gbnAuYXJyYXkoc2FtcGxlcykKCiAgICAgICAgICAgICMgTm9ybWFsaXplIHRvIGZsb2F0IGJldHdlZW4gLTEgYW5kIDEKICAgICAgICAgICAgcmV0dXJuIGF1ZGlvX2FycmF5LmFzdHlwZShucC5mbG9hdDMyKSAvIG5wLmlpbmZvKHNhbXBsZXMudHlwZWNvZGUpLm1heAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBucC5mcm9tYnVmZmVyKGF1ZGlvLCBkdHlwZT1ucC5pbnQxNikgLyAzMjc2OC4wCgoKZGVmIF9nZXRfZW5naW5lKGVuZ2luZTogc3RyLCBmaWxlX2Zvcm1hdDogc3RyLCAqKmt3YXJncykgLT4gU3BlZWNoRW5naW5lOgogICAgIyBlbGltaW5hdGUgdGhlIE5vbmUgdmFsdWVzOgogICAga3dhcmdzID0ge2tleTogdmFsdWUgZm9yIGtleSwgdmFsdWUgaW4ga3dhcmdzLml0ZW1zKCkgaWYgdmFsdWUgaXMgbm90IE5vbmV9CgogICAgaWYgZW5naW5lID09ICJiYXJrIjoKICAgICAgICByZXR1cm4gQmFya0VuZ2luZSgqKmt3YXJncykKICAgIGVsaWYgZW5naW5lID09ICJvcGVuYWkiOgogICAgICAgIHJldHVybiBPcGVuQUlFbmdpbmUoZmlsZV9mb3JtYXQ9ZmlsZV9mb3JtYXQsICoqa3dhcmdzKQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBlbmdpbmUuIFRoZSBwYXJhbWV0ZXIgYGVuZ2luZWAgbXVzdCBiZSBlaXRoZXIgJ2JhcmsnIG9yICdvcGVuYWknLiBHaXZlbjoge2VuZ2luZX0iCiAgICAgICAgKQoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfc3BsaXRfbGluZShsaW5lOiBzdHIsIG1heF9sZW5ndGg6IGludCA9IDI1MCkgLT4gTGlzdFtzdHJdOgogICAgaWYgbGVuKGxpbmUpIDwgbWF4X2xlbmd0aDoKICAgICAgICByZXR1cm4gW2xpbmVdCgogICAgc2VudGVuY2VzID0gWwogICAgICAgIGYie3NlbnRlbmNlLnN0cmlwKCl9LiIgZm9yIHNlbnRlbmNlIGluIGxpbmUuc3BsaXQoIi4iKSBpZiBzZW50ZW5jZS5zdHJpcCgpCiAgICBdCgogICAgc3BsaXRzID0gW10KICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlc1swXSkKICAgIHNwbGl0ID0gc2VudGVuY2VzWzBdCiAgICBmb3Igc2VudGVuY2UgaW4gc2VudGVuY2VzWzE6XToKICAgICAgICBpZiBjdXJyZW50X2xlbmd0aCArIGxlbihzZW50ZW5jZSkgPiBtYXhfbGVuZ3RoOgogICAgICAgICAgICBzcGxpdHMuYXBwZW5kKHNwbGl0KQogICAgICAgICAgICBzcGxpdCA9IHNlbnRlbmNlCiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoID0gbGVuKHNlbnRlbmNlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGN1cnJlbnRfbGVuZ3RoICs9IGxlbihzZW50ZW5jZSkKICAgICAgICAgICAgc3BsaXQgKz0gIiAiICsgc2VudGVuY2UKICAgIGlmIHNwbGl0OgogICAgICAgIHNwbGl0cy5hcHBlbmQoc3BsaXQpCgogICAgcmV0dXJuIHNwbGl0cwoKCmRlZiBfZ2V0X2xvZ2dlcigpOgogICAgZ2xvYmFsIF9MT0dHRVIKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgIyBDaGVjayBpZiBNTFJ1biBpcyBhdmFpbGFibGU6CiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICByZXR1cm4gY29udGV4dC5sb2dnZXIKICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yOgogICAgICAgIHJldHVybiBfTE9HR0VSCg==
    +metadata:
    +  categories:
    +  - data-generation
    +  - audio
    +  tag: ''
    +  name: text-to-audio-generator
     kind: job
    +verbose: false
     
             
         
    diff --git a/functions/master/text_to_audio_generator/latest/static/item.html b/functions/master/text_to_audio_generator/latest/static/item.html index 4fc64dfb..2aeea892 100644 --- a/functions/master/text_to_audio_generator/latest/static/item.html +++ b/functions/master/text_to_audio_generator/latest/static/item.html @@ -30,9 +30,8 @@ apiVersion: v1 categories: -- data-preparation -- machine-learning -- pytorch +- data-generation +- audio description: Generate audio file from text using different speakers doc: '' example: text_to_audio_generator.ipynb diff --git a/functions/master/text_to_audio_generator/latest/static/text_to_audio_generator.html b/functions/master/text_to_audio_generator/latest/static/text_to_audio_generator.html index c9c93a42..f807a73b 100644 --- a/functions/master/text_to_audio_generator/latest/static/text_to_audio_generator.html +++ b/functions/master/text_to_audio_generator/latest/static/text_to_audio_generator.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/1.1.0/static/documentation.html b/functions/master/tf2_serving/1.1.0/static/documentation.html index 7124689a..b92a2870 100644 --- a/functions/master/tf2_serving/1.1.0/static/documentation.html +++ b/functions/master/tf2_serving/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/1.1.0/static/example.html b/functions/master/tf2_serving/1.1.0/static/example.html index bded5cf6..a27ca4ac 100644 --- a/functions/master/tf2_serving/1.1.0/static/example.html +++ b/functions/master/tf2_serving/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/1.1.0/static/tf2_serving.html b/functions/master/tf2_serving/1.1.0/static/tf2_serving.html index 9e33ec1e..de5fa741 100644 --- a/functions/master/tf2_serving/1.1.0/static/tf2_serving.html +++ b/functions/master/tf2_serving/1.1.0/static/tf2_serving.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/latest/static/documentation.html b/functions/master/tf2_serving/latest/static/documentation.html index 7124689a..b92a2870 100644 --- a/functions/master/tf2_serving/latest/static/documentation.html +++ b/functions/master/tf2_serving/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/latest/static/example.html b/functions/master/tf2_serving/latest/static/example.html index bded5cf6..a27ca4ac 100644 --- a/functions/master/tf2_serving/latest/static/example.html +++ b/functions/master/tf2_serving/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/tf2_serving/latest/static/tf2_serving.html b/functions/master/tf2_serving/latest/static/tf2_serving.html index 9e33ec1e..de5fa741 100644 --- a/functions/master/tf2_serving/latest/static/tf2_serving.html +++ b/functions/master/tf2_serving/latest/static/tf2_serving.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/transcribe/1.2.0/src/data/error_file.txt b/functions/master/transcribe/1.2.0/src/data/error_file.txt new file mode 100644 index 00000000..e69de29b diff --git a/functions/master/transcribe/1.2.0/src/data/speech_01.mp3 b/functions/master/transcribe/1.2.0/src/data/speech_01.mp3 new file mode 100644 index 00000000..ae0e5c82 Binary files /dev/null and b/functions/master/transcribe/1.2.0/src/data/speech_01.mp3 differ diff --git a/functions/master/transcribe/1.2.0/src/data/speech_02.mp3 b/functions/master/transcribe/1.2.0/src/data/speech_02.mp3 new file mode 100644 index 00000000..1d5e6c03 Binary files /dev/null and b/functions/master/transcribe/1.2.0/src/data/speech_02.mp3 differ diff --git a/functions/master/transcribe/1.2.0/src/function.yaml b/functions/master/transcribe/1.2.0/src/function.yaml new file mode 100644 index 00000000..43e9b3a8 --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/function.yaml @@ -0,0 +1,285 @@ +kind: job +metadata: + categories: + - audio + - genai + tag: '' + name: transcribe +verbose: false +spec: + build: + origin_filename: '' + requirements: + - transformers + - tqdm + - torchaudio + - torch + - accelerate + base_image: mlrun/mlrun + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz + disable_auto_mount: false + description: Transcribe audio files into text files + image: '' + command: '' + default_handler: transcribe + entry_points: + do_task: + name: do_task + doc: Try to perform the task storing an error if occurred. + lineno: 348 + parameters: + - name: self + has_varargs: false + has_kwargs: false + is_failed: + name: is_failed + doc: Check if the task failed. + lineno: 70 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: Whether the task failed. + type: bool + get_result: + name: get_result + doc: 'Get the result of the task. If the task failed, the error will be returned, + otherwise, the result will be the + + text file name.' + lineno: 78 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: The task's result. + type: Tuple[str, str] + to_tuple: + name: to_tuple + doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing + to pass in queue). + lineno: 358 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: The converted task. + type: Tuple[str, dict] + transcription_output_channels: + name: transcription_output_channels + doc: Get the transcription output channels. + lineno: 340 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: The transcription output channels. + type: List[Tuple[str, dict]] + process_batch: + name: process_batch + doc: 'Process a batch of transcriptions. Tasks related to the given batch will + be created and stored in the batch + + processor.' + lineno: 575 + parameters: + - name: self + - name: batch + type: List[dict] + doc: The batch of transcriptions to process. + has_varargs: false + has_kwargs: false + get_tasks: + name: get_tasks + doc: Get the tasks to perform. + lineno: 453 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: The tasks to perform. + type: List[BaseTask] + do_tasks: + name: do_tasks + doc: Perform the tasks. Should be used if no multiprocessing queue is given + to a transcriber. + lineno: 463 + parameters: + - name: self + has_varargs: false + has_kwargs: false + get_results: + name: get_results + doc: Get the results of the tasks. The stored results are then cleared. + lineno: 471 + parameters: + - name: self + has_varargs: false + has_kwargs: false + outputs: + - doc: The results of the tasks. + type: List[Tuple[bool, Tuple[str, str]]] + load: + name: load + doc: Load the transcriber. Must be called before transcribing. + lineno: 695 + parameters: + - name: self + has_varargs: false + has_kwargs: false + transcribe: + name: transcribe + doc: "Transcribe audio files into text files and collect additional data. The\ + \ end result is a directory of transcribed\ntext files and a dataframe containing\ + \ the following columns:\n\n* audio_file - The audio file path.\n* transcription_file\ + \ - The transcribed text file name in the output directory.\n\nThe transcription\ + \ is based on Huggingface's ASR pipeline -\nhttps://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline\ + \ and\nis tested with OpenAI's Whisper models - https://huggingface.co/openai.\n\ + \nIf one of the speaker diarization parameters are given (either `speech_diarization`\ + \ or\n`speech_diarize_per_channel`), the transcription will be written in\ + \ a conversation format, where each speaker will\nbe written in a separate\ + \ line::\n\n speaker_1: text\n speaker_2: text\n speaker_1: text\n\ + \ ..." + lineno: 1097 + parameters: + - name: data_path + type: Union[str, Path, List[Union[str, Path]]] + doc: A directory of audio files or a single file or a list of files to transcribe. + - name: output_directory + type: str + doc: Path to a directory to save all transcribed audio files. If not given, + will save the transcribed files in a temporary directory. + default: null + - name: model_name + type: str + doc: 'The model name to use. Should be a model from the OpenAI''s Whisper + models for best results (for example "tiny", "base", "large", etc.). See + here for more information: https://huggingface.co/openai?search_models=whisper.' + default: openai/whisper-tiny + - name: device + type: str + doc: The device to use for inference. If not given, will use GPU if available. + default: null + - name: use_flash_attention_2 + type: bool + doc: 'Whether to use the Flash Attention 2 implementation. It can be used + only with one of the following GPUs: Nvidia H series and Nvidia A series. + T4 support will be available soon.' + default: null + - name: use_better_transformers + type: bool + doc: Whether to use the Better Transformers library to further optimize the + model. Should be used for all use cases that do not support flash attention + 2. + default: null + - name: assistant_model + type: str + doc: 'The assistant model name to use for inference. Notice that the optimizations + (flash attention 2 and better transformers) will be applied for the assistant + as well. Should be a model from Huggingface''s distil-whisper (see here + for more information: https://github.com/huggingface/distil-whisper).' + default: null + - name: max_new_tokens + type: int + doc: The maximum number of new tokens to generate. This is used to limit the + generation length. Default is 128 tokens. + default: 128 + - name: chunk_length_s + type: int + doc: The audio chunk to split the audio to (in seconds). Default is 30 seconds. + default: 30 + - name: batch_size + type: int + doc: The batch size to use for inference. Default is 2. + default: 8 + - name: spoken_language + type: str + doc: Aim whisper to know what language is spoken. If None, it will try to + detect it. + default: null + - name: translate_to_english + type: bool + doc: Whether to translate the transcriptions to English. + default: false + - name: speech_diarization + type: Dict[str, List[Tuple[float, float, str]]] + doc: 'A speech diarization dictionary with the file names to transcribe as + keys and their diarization as value. The diarization is a list of tuples: + (start, end, speaker). An example for a diarization dictionary::' + default: null + - name: speech_diarize_per_channel + type: int + doc: 'Perform speech diarization per channel. Each speaker is expected to + belong to a separate channel in the audio. Notice: This will make the transcription + slower as each channel wil be transcribed separatly. If a speech diarization + is passed (via the `speech_diarization` parameter), this parameter is ignored.' + default: null + - name: speaker_labels + type: List[str] + doc: A list of speaker labels by channel order to use for writing the transcription + with respect to per channel speech diarization. This won't be used together + with a given speech diarization (via the `speech_diarization` parameter). + default: null + - name: use_multiprocessing + type: Union[bool, int] + doc: 'Whether to use multiprocessing to transcribe the audio files. Can be + either a boolean value or an integer. If `True`, will use the default amount + of workers (3): 1 for transcription, 1 for batch processing and 1 for task + completion (such as speech diarization and writing to files). To control + the amount of tasks completion workers, an integer can be provided to specify + the amount of workers. `False`, will use a single process. Default is `False`.' + default: false + - name: verbose + type: bool + doc: Whether to print the progress of the transcription. Default is `False`. + default: false + has_varargs: false + has_kwargs: false + audio_iterator: + name: audio_iterator + doc: '' + lineno: 804 + has_varargs: false + has_kwargs: false + outputs: + - type: Generator[Union[dict, str], None, None] + batch_iterator: + name: batch_iterator + doc: '' + lineno: 816 + has_varargs: false + has_kwargs: false + outputs: + - type: Generator[List[Union[dict, str]], None, None] + open_mpi_handler: + name: open_mpi_handler + doc: '' + lineno: 957 + parameters: + - name: worker_inputs + type: List[str] + - name: root_worker_inputs + type: Dict[str, Any] + default: null + has_varargs: false + has_kwargs: false + decorator: + name: decorator + doc: '' + lineno: 969 + parameters: + - name: handler + has_varargs: false + has_kwargs: false + wrapper: + name: wrapper + doc: '' + lineno: 974 + has_varargs: false + has_kwargs: true diff --git a/functions/master/transcribe/1.2.0/src/item.yaml b/functions/master/transcribe/1.2.0/src/item.yaml new file mode 100644 index 00000000..6deaf710 --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/item.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +categories: +- audio +- genai +description: Transcribe audio files into text files +doc: '' +example: transcribe.ipynb +generationDate: 2023-07-13:11-20 +hidden: false +icon: '' +labels: + author: yonatans +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: transcribe +platformVersion: 3.5.3 +spec: + filename: transcribe.py + handler: transcribe + image: mlrun/mlrun + kind: job + requirements: + - transformers + - tqdm + - torchaudio + - torch + - accelerate +url: '' +version: 1.2.0 \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/src/requirements.txt b/functions/master/transcribe/1.2.0/src/requirements.txt new file mode 100644 index 00000000..d16bfc9d --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/requirements.txt @@ -0,0 +1,5 @@ +transformers +torch +torchaudio +tqdm +accelerate \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/src/test_transcribe.py b/functions/master/transcribe/1.2.0/src/test_transcribe.py new file mode 100644 index 00000000..f70b3856 --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/test_transcribe.py @@ -0,0 +1,104 @@ +# Copyright 2019 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os +import pathlib +import tempfile +from difflib import SequenceMatcher + +import mlrun +import pytest + + +expected_outputs = [ + "This is a speech to text test.", + "In the heart of the stadium, " + "cheers paint the air as the ball weaves its tale across the pitch. " + "With each kick, players chase their dreams, guided by the rhythmic dance of teamwork. " + "The crowd roars, a symphony of passion, " + "as the game writes its unpredictable story on the field of destiny.", +] +models = [ + + "openai/whisper-tiny", +] + + +@pytest.mark.skipif(os.system("which ffmpeg") != 0, reason="ffmpeg not installed") +@pytest.mark.parametrize("model_name", models) +@pytest.mark.parametrize("audio_path", ["./data", "./data/speech_01.mp3"]) +def test_transcribe(model_name: str, audio_path: str): + # Setting variables and importing function: + artifact_path = tempfile.mkdtemp() + project = mlrun.get_or_create_project("test") + transcribe_function = project.set_function("transcribe.py", "transcribe", kind="job", image="mlrun/mlrun") + # transcribe_function = mlrun.import_function("function.yaml") + temp_dir = tempfile.mkdtemp() + + # Running transcribe function: + transcribe_run = transcribe_function.run( + handler="transcribe", + params={ + "data_path": audio_path, + "model_name": model_name, + "device": "cpu", + "output_directory": temp_dir, + }, + local=True, + returns=["output_dir: path", "dataset: dataset", "errored_files"], + artifact_path=artifact_path, + ) + + artifact_path += ( + f"/{transcribe_run.metadata.name}/{transcribe_run.metadata.iteration}/" + ) + + # Getting actual files from run (text and errored): + input_files = ( + os.listdir(audio_path) + if pathlib.Path(audio_path).is_dir() + else [pathlib.Path(audio_path).name] + ) + expected_text_files = sorted([f for f in input_files if f.endswith("mp3")]) + error_files = list(set(input_files) - set(expected_text_files)) + expected_text_files = [f.replace("mp3", "txt") for f in expected_text_files] + text_files = sorted(os.listdir(temp_dir)) + + # Check that the text files are saved in output_directory: + assert text_files == expected_text_files + + # Check that the transcribed text was approximately (90%) generated from audio: + for text_file, expected in zip(text_files, expected_outputs): + with open(os.path.join(temp_dir, text_file), "r") as f: + output = f.readlines()[0] + ratio = SequenceMatcher(None, expected, output).ratio() + assert ratio >= 0.9 + + # Check that the dataframe is in the correct size: + df = mlrun.get_dataitem(artifact_path + "dataset.parquet").as_df() + assert len(df) == len(expected_text_files) + + # Check errored files: + if isinstance(transcribe_run.outputs["errored_files"], str): + actual_errored_files = [] + else: + actual_errored_files = [ + os.path.basename(errored) + for errored in transcribe_run.outputs["errored_files"].keys() + ] + assert actual_errored_files == error_files + + # Check output_dir: + zip_dir = mlrun.get_dataitem(artifact_path + "output_dir.zip") + assert zip_dir.kind == "file" diff --git a/functions/master/transcribe/1.2.0/src/transcribe.ipynb b/functions/master/transcribe/1.2.0/src/transcribe.ipynb new file mode 100644 index 00000000..5671160c --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/transcribe.ipynb @@ -0,0 +1,338 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a80305ba-ffff-4116-aa46-5c1b67368239", + "metadata": {}, + "source": [ + "# Transcribe tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bdb947f0-5b9a-492d-9676-374c38eee14a", + "metadata": { + "ExecuteTime": { + "start_time": "2023-07-16T17:13:48.565039Z", + "end_time": "2023-07-16T17:14:01.952515Z" + } + }, + "outputs": [], + "source": [ + "import tempfile\n", + "import mlrun" + ] + }, + { + "cell_type": "markdown", + "id": "b7364965-8dcd-419a-8764-dd0c87edb9f8", + "metadata": {}, + "source": [ + "## Importing the transcribe function from hub\n", + "\n", + "To import the function directly from hub, use:\n", + "```python \n", + "transcribe_fn = mlrun.import_function(\"hub://transcribe\")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [], + "source": [ + "artifact_path = tempfile.mkdtemp()" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "start_time": "2023-07-16T17:14:01.954022Z", + "end_time": "2023-07-16T17:14:01.955760Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2d9a80a2-8448-49cd-a92f-1ab2072fc720", + "metadata": { + "ExecuteTime": { + "start_time": "2023-07-16T17:14:01.956508Z", + "end_time": "2023-07-16T17:14:01.966758Z" + } + }, + "outputs": [], + "source": [ + "transcribe_fn = mlrun.import_function(\"function.yaml\")" + ] + }, + { + "cell_type": "markdown", + "id": "7fcb6c8a-f83b-42d9-b02e-9187e85fe232", + "metadata": {}, + "source": [ + "## Running transcribe" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1570b05f-cfb7-466d-84c8-98f4c9d54ad4", + "metadata": { + "ExecuteTime": { + "start_time": "2023-07-16T17:14:01.969912Z", + "end_time": "2023-07-16T17:14:12.724086Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:01,968 [info] Storing function: {'name': 'transcribe-transcribe', 'uid': 'd1384cb679bc4c178b0195d964b628a8', 'db': None}\n", + "> 2023-07-16 17:14:01,969 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:01,969 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:01,970 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:01,970 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:01,972 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:01,972 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:09,804 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:09,805 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:09,805 [info] Loading whisper model: 'tiny'\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.\n", + "IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:10,374 [info] Model loaded.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Transcribing: 67%|██████▋ | 2/3 [00:02<00:01, 1.04s/file]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:12,556 [warning] Error in file: '/Users/Yonatan_Shelach/projects/functions/transcribe/data/error_file.txt'\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Transcribing: 100%|██████████| 3/3 [00:02<00:00, 1.39file/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:12,566 [info] Done:\n", + " audio_file transcription_file language length rate_of_speech\n", + "0 speech_01.mp3 speech_01.txt en 2.011333 3.480278\n", + "1 speech_02.mp3 speech_02.txt en 20.793500 2.548873\n", + "> 2023-07-16 17:14:12,596 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:12,597 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:12,659 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:12,660 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:12,671 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:12,672 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:12,707 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:12,707 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n", + "> 2023-07-16 17:14:12,708 [warning] Could not detect path to API server, not connected to API server!\n", + "> 2023-07-16 17:14:12,708 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect\n" + ] + }, + { + "data": { + "text/plain": "", + "text/html": "\n
    \n
    \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    default
    ...b628a8
    0Jul 16 14:14:01completedtranscribe-transcribe
    kind=
    owner=Yonatan_Shelach
    host=M-QWXQJK77Q0
    model_name=tiny
    audio_files_directory=./data
    decoding_options={'fp16': False}
    output_directory=./output
    transcriptions
    transcriptions_df
    transcriptions_errors
    \n
    \n
    \n
    \n Title\n ×\n
    \n \n
    \n
    \n" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": "", + "text/html": " > to track results use the .show() or .logs() methods " + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-07-16 17:14:12,721 [info] Run execution finished: {'status': 'completed', 'name': 'transcribe-transcribe'}\n" + ] + } + ], + "source": [ + "transcribe_run = transcribe_fn.run(\n", + " handler=\"transcribe\",\n", + " params={\n", + " \"model_name\": \"tiny\",\n", + " \"input_path\": \"./data\",\n", + " \"decoding_options\": {\"fp16\": False},\n", + " \"output_directory\": \"./output\",\n", + " },\n", + " returns=[\n", + " \"transcriptions: path\",\n", + " \"transcriptions_df: dataset\",\n", + " {\"key\": \"transcriptions_errors\", \"artifact_type\": \"file\", \"file_format\": \"yaml\"},\n", + " ],\n", + " local=True,\n", + " artifact_path=artifact_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "407d1e6c-d2a4-42e7-b3e2-c51138cb30ea", + "metadata": { + "ExecuteTime": { + "start_time": "2023-07-16T17:14:12.726898Z", + "end_time": "2023-07-16T17:14:12.745521Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": "{'transcriptions': 'store://artifacts/default/transcribe-transcribe_transcriptions:d1384cb679bc4c178b0195d964b628a8',\n 'transcriptions_df': 'store://artifacts/default/transcribe-transcribe_transcriptions_df:d1384cb679bc4c178b0195d964b628a8',\n 'transcriptions_errors': 'store://artifacts/default/transcribe-transcribe_transcriptions_errors:d1384cb679bc4c178b0195d964b628a8'}" + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "transcribe_run.outputs" + ] + }, + { + "cell_type": "markdown", + "source": [ + "**Notice**: If connected to mlrun server, you can simply use:\n", + "\n", + "```python\n", + "df = transcribe_run.artifact(\"transcriptions_df\")\n", + "```" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [], + "source": [ + "artifact_path += f\"/{transcribe_run.metadata.name}/{transcribe_run.metadata.iteration}/\"" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "start_time": "2023-07-16T17:14:12.730064Z", + "end_time": "2023-07-16T17:14:12.748292Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 10, + "outputs": [], + "source": [ + "df = mlrun.get_dataitem(artifact_path + \"transcriptions_df.parquet\").as_df()" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "start_time": "2023-07-16T17:25:02.712455Z", + "end_time": "2023-07-16T17:25:02.719538Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [ + { + "data": { + "text/plain": " audio_file transcription_file language length rate_of_speech\n0 speech_01.mp3 speech_01.txt en 2.011333 3.480278\n1 speech_02.mp3 speech_02.txt en 20.793500 2.548873", + "text/html": "
    \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    audio_filetranscription_filelanguagelengthrate_of_speech
    0speech_01.mp3speech_01.txten2.0113333.480278
    1speech_02.mp3speech_02.txten20.7935002.548873
    \n
    " + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "start_time": "2023-07-16T17:25:07.878158Z", + "end_time": "2023-07-16T17:25:07.880514Z" + } + } + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "language": "python", + "display_name": "Python 3 (ipykernel)" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/transcribe/1.2.0/src/transcribe.py b/functions/master/transcribe/1.2.0/src/transcribe.py new file mode 100644 index 00000000..9cabcb1e --- /dev/null +++ b/functions/master/transcribe/1.2.0/src/transcribe.py @@ -0,0 +1,1464 @@ +# Copyright 2024 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import operator +import os +import tempfile +from functools import reduce, wraps +from multiprocessing import Process, Queue +from pathlib import Path +from typing import Any, Dict, Generator, List, Literal, NamedTuple, Tuple, Union + +import pandas as pd +import torch +import torchaudio +from tqdm import tqdm +from transformers import ( + AutomaticSpeechRecognitionPipeline, + AutoModelForCausalLM, + pipeline, +) +from transformers.utils import is_flash_attn_2_available + + +class BaseTask: + """ + A task to write the transcription to file. + """ + + def __init__( + self, audio_file: Path, transcription_output: Union[dict, str], text_file: Path + ): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param transcription_output: The transcription output from the pipeline. String means an exception was raised. + :param text_file: Path to the text file to write the transcription to. + """ + # Store the parameters: + self._audio_file = audio_file + self._transcription_output = transcription_output + self._text_file = text_file + + # Prepare the error variable: + self._error: str = None + + def do_task(self): + """ + Try to perform the task storing an error if occurred. + """ + if isinstance(self._transcription_output, str): + self._error = self._transcription_output + return + try: + self._do_task() + except Exception as exception: + self._error = str(exception) + + def is_failed(self) -> bool: + """ + Check if the task failed. + + :returns: Whether the task failed. + """ + return self._error is not None + + def get_result(self) -> Tuple[str, str]: + """ + Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the + text file name. + + :returns: The task's result. + """ + if self.is_failed(): + return self._audio_file.name, self._error + return self._audio_file.name, self._text_file.name + + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + return self.__class__.__name__, { + "audio_file": self._audio_file, + "transcription_output": self._transcription_output, + "text_file": self._text_file, + } + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path. + """ + # Checking for no duplications: + i = 1 + while self._text_file.exists(): + i += 1 + self._text_file = ( + self._text_file.parent + / f"{self._text_file.stem.rsplit('_', 1)[0]}_{i}{self._text_file.suffix}" + ) + + # Make sure all directories are created: + self._text_file.parent.mkdir(exist_ok=True, parents=True) + + # Write to file: + with open(self._text_file, "w") as fp: + fp.write(self._transcription_output["text"]) + + +class SpeechDiarizationTask(BaseTask): + """ + A task to write the transcription to file with respect to a given speech diarization. + """ + + class _DiarizationSegment(NamedTuple): + """ + A speech diarization segment. + """ + + start: float + end: float + speaker: str + + class _WordTimestamp(NamedTuple): + """ + A word with its start and end timestamps. + """ + + start: float + end: float + text: str + + def __init__( + self, + audio_file: Path, + transcription_output: dict, + text_file: Path, + speech_diarization: List[Tuple[float, float, str]], + ): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param transcription_output: The transcription output from the pipeline. + :param text_file: Path to the text file to write the transcription to. + :param speech_diarization: A speech diarization as a list of tuples: (start, end, speaker). + """ + super().__init__( + audio_file=audio_file, + transcription_output=transcription_output, + text_file=text_file, + ) + self._speech_diarization = speech_diarization + self._segments: List[SpeechDiarizationTask._DiarizationSegment] = None + self._last_chosen_index = 0 + + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + return task_class, { + **task_kwargs, + "speech_diarization": self._speech_diarization, + } + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path with respect to the given speech diarization. + """ + # Check if a speech diarization is given, if not, just write the transcription to file: + if not self._speech_diarization: + super()._do_task() + return + + # Cast the chunks to word timestamps tuples: + words = [ + SpeechDiarizationTask._WordTimestamp( + start=chunk["timestamp"][0], + end=chunk["timestamp"][1], + text=chunk["text"], + ) + for chunk in self._transcription_output["chunks"] + ] + + # Cast speech diarization to segments tuples: + self._segments = [ + SpeechDiarizationTask._DiarizationSegment(*segment) + for segment in self._speech_diarization + ] + + # Try to match the Whisper model predicted timestamps to the closest diarization segment (closest diarization + # segment will be the most overlapping with the word, and if there is no overlap, the closest segment to the + # word): + speaker = self._segments[self._last_chosen_index].speaker + text = f"{speaker}:" + for word in words: + # Get the next diarization segment: + self._get_next_segment(word=word) + # Check if the segment is of the same speaker: + if self._segments[self._last_chosen_index].speaker == speaker: + # Collect the word: + text += word.text + else: + # Append a newline and update the new speaker: + speaker = self._segments[self._last_chosen_index].speaker + text += f"\n{speaker}:{word.text}" + + # Update the transcription output with the new text to write it to file: + self._transcription_output["text"] = text + super()._do_task() + + def _get_next_segment( + self, + word: _WordTimestamp, + ): + """ + Get the next diarization segment the given word falls into. The `self._last_chosen_index` will be updated + accordingly. + + :param word: The word timestamp to match to the next segment. + """ + # If the last chosen segment is the last segment, return it: + if self._last_chosen_index == len(self._segments) - 1: + return + + # Get the last chosen diarization segment: + last_chosen = self._segments[self._last_chosen_index] + + # None value may appear if the word is the last word in the audio file, or it was split during inference. In + # that case, we'll set the last segment: + if word.end is None: + self._last_chosen_index = len(self._segments) - 1 + return + + # If the word ends before the last chosen segment: + if word.end <= last_chosen.start: + # Then it is still the closest segment + return + + # We check if it ends inside the last chosen segment: + if word.end < last_chosen.end: + # Then it still is the closest segment + return + + # The word ends after the segment, we need to collect all next segments up until the word ends before them: + possible_segments = [self._last_chosen_index] + for i in range(self._last_chosen_index + 1, len(self._segments)): + if word.end > self._segments[i].end: + possible_segments.append(i) + continue + possible_segments.append(i) + break + + # Check for the most overlapping option: + best_overlap = 0 + most_overlapping_segment_index = None + for i in possible_segments: + # If the word starts before segment: + if word.start <= self._segments[i].start: + # If it ends before the segment, there is an overlap from the start of the segment to the end of the + # word: + if word.end < self._segments[i].end: + overlap = word.end - self._segments[i].start + else: + # The word is wrapping the segment, the overlap is the segment's length: + overlap = self._segments[i].end - self._segments[i].start + # The word starts in segment, check if the word ends in it: + elif word.end < self._segments[i].end: + # The overlap is the word's length: + overlap = word.end - word.start + # The word start in segment but ends after it, the overlap is from the word's start to the segment's end: + else: + overlap = self._segments[i].end - word.start + # Check for new best overlap: + if overlap > best_overlap: + best_overlap = overlap + most_overlapping_segment_index = i + if most_overlapping_segment_index is not None: + self._last_chosen_index = most_overlapping_segment_index + return + + # If there is no overlapping segment, return the closest segment: + best_distance = None + closest_segment_index = None + for i in possible_segments: + distance = ( + word.start - self._segments[i].end + if word.start > self._segments[i].end + else self._segments[i].start - word.end + ) + if best_distance is None or distance < best_distance: + best_distance = distance + closest_segment_index = i + self._last_chosen_index = closest_segment_index + + +class SpeechDiarizationPerChannelTask(BaseTask): + """ + A task to write the transcription to file with respect to a given speech diarization per channel. + """ + + class _WordTimestamp(NamedTuple): + """ + A word with its start and end timestamps and speaker label (channel the word was taken from). + """ + + start: float + end: float + speaker: str + text: str + + def __init__(self, audio_file: Path, text_file: Path): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param text_file: Path to the text file to write the transcription to. + """ + super().__init__( + audio_file=audio_file, transcription_output={}, text_file=text_file + ) + self._transcription_output_channels: List[Tuple[str, dict]] = [] + + @property + def transcription_output_channels(self) -> List[Tuple[str, dict]]: + """ + Get the transcription output channels. + + :returns: The transcription output channels. + """ + return self._transcription_output_channels + + def do_task(self): + """ + Try to perform the task storing an error if occurred. + """ + for _, channel_output in self._transcription_output_channels: + if isinstance(channel_output, str): + self._error = self._transcription_output_channels + return + super().do_task() + + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + task_kwargs.pop("transcription_output") + return task_class, task_kwargs + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path with respect to the given speech diarization + per channel. + """ + # Cast the chunks to word timestamps tuples: + words_per_channel = [ + [ + SpeechDiarizationPerChannelTask._WordTimestamp( + start=chunk["timestamp"][0], + end=chunk["timestamp"][1], + speaker=speaker, + text=chunk["text"], + ) + for chunk in output["chunks"] + ] + for speaker, output in self._transcription_output_channels + ] + + # Merge and sort the words per channel by their start time: + words = operator.add(*words_per_channel) + words.sort() + + # Write the transcription to file: + current_speaker = words[0].speaker + text = f"{current_speaker}:" + for word in words: + # Check if the word's speaker is different from the current one: + if word.speaker != current_speaker: + # Append a newline and update the new speaker: + current_speaker = word.speaker + text += f"\n{current_speaker}:" + # Collect the word: + text += word.text + + # Update the transcription output with the new text to write it to file: + self._transcription_output["text"] = text + super()._do_task() + + +class BatchProcessor: + """ + A batch processor to process batches of transcriptions. The batch processor is creating tasks and is aimed to be + working along the transcriber. It can be used with multiprocessing queue or run the tasks directly using the + associated methods. + """ + + def __init__(self, audio_files: List[Path], output_directory: Path): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + """ + # Store the parameters: + self._audio_files = audio_files + self._output_directory = output_directory + + # Prepare the batching variables: + self._current_file_index = 0 + self._tasks: List[BaseTask] = [] + self._results: List[Tuple[bool, Tuple[str, str]]] = [] + + def process_batch(self, batch: List[Union[dict, str]]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Get the relevant files belongs to the given batch: + current_files = self._get_current_files(batch_size=len(batch)) + + # Build the diarization tasks: + self._tasks.extend( + [ + BaseTask( + audio_file=file, + transcription_output=batch[i], + text_file=self._output_directory / f"{file.stem}.txt", + ) + for i, file in enumerate(current_files) + ] + ) + + def get_tasks(self) -> List[BaseTask]: + """ + Get the tasks to perform. + + :returns: The tasks to perform. + """ + tasks = self._tasks + self._tasks = [] + return tasks + + def do_tasks(self): + """ + Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber. + """ + for task in self.get_tasks(): + task.do_task() + self._results.append((task.is_failed(), task.get_result())) + + def get_results(self) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Get the results of the tasks. The stored results are then cleared. + + :returns: The results of the tasks. + """ + results = self._results + self._results = [] + return results + + def _get_current_files(self, batch_size: int) -> List[Path]: + """ + Get the current files to process. + + :param batch_size: The batch size to progress the current file index. + + :returns: The current files to process. + """ + end_index = ( + self._current_file_index + batch_size + if self._current_file_index + batch_size < len(self._audio_files) + else len(self._audio_files) + ) + current_files = self._audio_files[self._current_file_index : end_index] + self._current_file_index = end_index + return current_files + + +class SpeechDiarizationBatchProcessor(BatchProcessor): + """ + A batch processor to process batches of transcriptions with respect to a given speech diarization. The batch + processor is creating tasks and is aimed to be working along the transcriber. It can be used with multiprocessing + queue or run the tasks directly using the associated methods. + """ + + def __init__( + self, audio_files: List[Path], output_directory: Path, speech_diarization: dict + ): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + :param speech_diarization: A speech diarization dictionary to pass along with each processed batch. + """ + super().__init__(audio_files=audio_files, output_directory=output_directory) + self._speech_diarization = speech_diarization + self._audio_files = audio_files + + def process_batch(self, batch: List[dict]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Get the relevant files belongs to the given batch: + current_files = self._get_current_files(batch_size=len(batch)) + + # Build the diarization tasks: + self._tasks.extend( + [ + SpeechDiarizationTask( + audio_file=file, + transcription_output=batch[i], + text_file=self._output_directory / f"{file.stem}.txt", + speech_diarization=self._speech_diarization.get(file.name), + ) + for i, file in enumerate(current_files) + ] + ) + + +class PerChannelSpeechDiarizationBatchProcessor(BatchProcessor): + """ + A batch processor to process batches of transcriptions per channel. The batch processor is creating tasks with the + selected amount of channels given and is aimed to be working along the transcriber. It can be used with + multiprocessing queue or run the tasks directly using the associated methods. + """ + + def __init__( + self, + audio_files: List[Path], + output_directory: Path, + n_channels: int, + speakers: List[str], + ): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + :param n_channels: The number of channels in each audio file to transcribe. + :param speakers: The speakers labels to use for each channel. + """ + super().__init__(audio_files=audio_files, output_directory=output_directory) + + # Store the parameters: + self._n_channels = n_channels + self._speakers = speakers + + # Prepare a channel buffer to store the channels until the current task created is fully covered: + self._task_in_process: SpeechDiarizationPerChannelTask = None + + def process_batch(self, batch: List[dict]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Go over the batch and create the tasks: + for output in batch: + # Check if there is a task in process: + if not self._task_in_process: + # Create a new task: + self._task_in_process = SpeechDiarizationPerChannelTask( + audio_file=self._audio_files[self._current_file_index], + text_file=self._output_directory + / f"{self._audio_files[self._current_file_index].stem}.txt", + ) + # Get the channel's speaker: + speaker = self._speakers[ + len(self._task_in_process.transcription_output_channels) + ] + # Collect the channel into the processed task: + self._task_in_process.transcription_output_channels.append( + (speaker, output) + ) + # Check if the task is fully covered (all channels are collected): + if ( + len(self._task_in_process.transcription_output_channels) + == self._n_channels + ): + # Collect the task and reset the task in process: + self._tasks.append(self._task_in_process) + self._current_file_index += 1 + self._task_in_process = None + + +class Transcriber: + """ + A transcription wrapper for the Huggingface's ASR pipeline - + https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline to + use with OpenAI's Whisper models - https://huggingface.co/openai. + """ + + def __init__( + self, + model_name: str, + device: str = None, + use_flash_attention_2: bool = None, + use_better_transformers: bool = None, + assistant_model: str = None, + max_new_tokens: int = 128, + chunk_length_s: int = 30, + batch_size: int = 2, + spoken_language: str = None, + translate_to_english: bool = False, + return_timestamps: Union[bool, Literal["word"]] = False, + per_channel_transcription: int = 0, + ): + """ + Initialize the transcriber. + + :param model_name: The model name to use. Should be a model from the OpenAI's Whisper models for + best results (for example "tiny", "base", "large", etc.). + :param device: The device to use for inference. If not given, will use GPU if available. + :param use_flash_attention_2: Whether to use the Flash Attention 2 implementation. It can be used only with + one of the following GPUs: Nvidia H series and Nvidia A series. T4 support + will be available soon. + + Note: If both `use_flash_attention_2` and + `use_better_transformers` are `None`, the optimization will be chosen + automatically according to the available resources. + + :param use_better_transformers: Whether to use the Better Transformers library to further optimize the model. + Should be used for all use cases that do not support flash attention 2. + + Note: If both `use_flash_attention_2` and `use_better_transformers` are + `None`, the optimization will be chosen automatically according to the + available resources. + :param assistant_model: The assistant model name to use for inference. Notice that the optimizations + (flash attention 2 and better transformers) will be applied for the assistant + as well. Should be a model from Huggingface's distil-whisper (see here for + more information: https://github.com/huggingface/distil-whisper). + :param max_new_tokens: The maximum number of new tokens to generate. This is used to limit the + generation length. Default is 128 tokens. + :param chunk_length_s: The audio chunk to split the audio to (in seconds). Default is 30 seconds. + :param batch_size: The batch size to use for inference. Default is 2. + :param spoken_language: Aim whisper to know what language is spoken. If None, it will try to detect it + for each chunk. + :param translate_to_english: Whether to translate the transcriptions to English. Default is False. + :param return_timestamps: Whether to return the timestamps of the words. If "word", will return the + timestamps of each word. If True will return the timestamps of each chunk. + Default is False. Aimed to be used for speech diarization. + :param per_channel_transcription: Whether to do per channel transcription. If needed to run per channel + transcription, pass the number of channels expected for each audio file here. + 0 means regular transcription (merge channels). + + Note: If `per_channel_transcription` is not 0, `batch_size` must be treated to + be the number of channels and not audio files. Aimed to be used for per + channel speech diarization. + """ + # Store loading parameters: + self._model_name = model_name + self._device = device + self._use_flash_attention_2 = use_flash_attention_2 + self._use_better_transformers = use_better_transformers + self._max_new_tokens = max_new_tokens + self._chunk_length_s = chunk_length_s + self._batch_size = batch_size + self._return_timestamps = return_timestamps + self._per_channel_transcription = per_channel_transcription + + # Store generation parameters: + self._assistant_model = assistant_model + self._spoken_language = spoken_language + self._translate_to_english = translate_to_english + + # Prepare the transcription objects: + self._transcription_pipeline: AutomaticSpeechRecognitionPipeline = None + self._generate_kwargs: dict = None + + def load(self): + """ + Load the transcriber. Must be called before transcribing. + """ + # Set the device and data type to use (prefer GPU if available): + device = torch.device( + self._device or "cuda" if torch.cuda.is_available() else "cpu" + ) + torch_dtype = torch.float16 if device.type == "cuda" else torch.float32 + + # Choose the optimization to use (in case the user did not specify any): + if ( + self._use_flash_attention_2 is None + and self._use_better_transformers is None + ): + # Prefer to use flash attention 2 if available and cuda device is supported (see GPU names to architecture + # here: https://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units#Tesla): + if device.type == "cuda" and is_flash_attn_2_available(): + cuda_device_name = torch.cuda.get_device_properties(device).name + if any( + cuda_device_name.startswith(gpu_name) + for gpu_name in [ + "NVIDIA A", # For Ampere architecture (e.g. A10, A30, A100) + "NVIDIA H", # For Hopper architecture (e.g. H100) + "NVIDIA L", # For Ada Lovelace architecture (e.g. L4, L40) + "NVIDIA RTX 30", # For Ada Lovelace architecture (RTX 30 series) + "NVIDIA RTX 40", # For Ada Lovelace architecture (RTX 40 series) + "NVIDIA RTX 50", # For Ada Lovelace architecture (RTX 50 series) + # Will be supported soon according to FlashAttention GitHub repo: + # https://github.com/Dao-AILab/flash-attention?tab=readme-ov-file#installation-and-features + # "NVIDIA T4", # For Turing architecture (only T4) + # "NVIDIA RTX 20", # For Turing architecture (RTX 20 series) + ] + ): + self._use_flash_attention_2 = True + else: + self._use_better_transformers = True + else: + self._use_better_transformers = True + + # Build the optimizations kwargs: + model_kwargs = { + "low_cpu_mem_usage": True, + "use_safetensors": True, + } + if self._use_flash_attention_2: + if _LOGGER: + _LOGGER.info( + "Using FlashAttention2 optimization - make sure the `flash-attn` package is installed via " + "`pip install -U flash-attn --no-build-isolation`" + ) + model_kwargs["attn_implementation"] = "flash_attention_2" + elif self._use_better_transformers: + if _LOGGER: + _LOGGER.info( + "Using BetterTransformers optimization - make sure the `optimum` package is installed via " + "`pip install -U optimum`" + ) + model_kwargs["attn_implementation"] = "sdpa" + + # Initialize the speech recognition pipeline: + self._transcription_pipeline = pipeline( + task="automatic-speech-recognition", + model=self._model_name, + model_kwargs=model_kwargs.copy(), + batch_size=self._batch_size, + max_new_tokens=self._max_new_tokens, + chunk_length_s=self._chunk_length_s, + return_timestamps=self._return_timestamps, + torch_dtype=torch_dtype, + device=device, + ) + + # Prepare the generation kwargs: + self._generate_kwargs = { + "language": self._spoken_language, + "task": "translate" if self._translate_to_english else "transcribe", + } + + # Initialize the assistant model (if needed): + if self._assistant_model: + assistant_model = AutoModelForCausalLM.from_pretrained( + self._assistant_model, torch_dtype=torch_dtype, **model_kwargs + ) + assistant_model.to(device) + self._generate_kwargs["assistant_model"] = assistant_model + + def transcribe( + self, + audio_files: List[Path], + batch_processor: BatchProcessor = None, + batches_queue: Queue = None, + verbose: bool = False, + ) -> Union[List[List[dict]], None]: + """ + Transcribe the given audio files. The transcriptions will be sent to a queue or a batch processor for further + processing like writing to text files. If no queue or batch processor is given, the transcriptions outputs from + the pipeline will be returned. Otherwise, `None` is returned. + + :param audio_files: The audio files to transcribe. + :param batch_processor: A batch processor. + :param batches_queue: A multiprocessing queue to put the batches in. + :param verbose: Whether to show a progress bar. Default is False. + + :returns: The transcriptions outputs from the pipeline if no queue or batch processor is given, otherwise, + `None`. + """ + # Wrap the audio files with a function to iterate over them via a generator (save memory and runtime with + # Huggingface's pipelines as they preload each input while inference is running): + def audio_iterator() -> Generator[Union[dict, str], None, None]: + if self._per_channel_transcription: + for audio_file in audio_files: + audio, sampling_rate = torchaudio.load(str(audio_file)) + audio = audio.numpy() + for channel in audio: + yield {"raw": channel, "sampling_rate": sampling_rate} + else: + for audio_file in audio_files: + yield str(audio_file) + + # Create a batch iterator: + def batch_iterator() -> Generator[List[Union[dict, str]], None, None]: + batch = [] + for audio in audio_iterator(): + batch.append(audio) + if len(batch) == self._batch_size: + yield batch + batch = [] + if batch: + yield batch + + # Prepare the successes dataframe and errors dictionary to be returned: + outputs = [] + + # Infer through the pipeline: + for input_batch in tqdm( + batch_iterator() if self._batch_size > 1 else audio_iterator(), + desc="Transcribing", + unit="channel" if self._per_channel_transcription else "audio file", + total=( + ( + (len(audio_files) // self._batch_size) + + (len(audio_files) % self._batch_size != 0) + ) + * (self._per_channel_transcription or 1) + ), + disable=not verbose, + ): + # Infer: + try: + output_batch = self._transcription_pipeline( + input_batch, + generate_kwargs=self._generate_kwargs, + ) + except Exception as exception: + # Collect the exception: + output_batch = str(exception) + # Align to batch size: + output_batch = ( + [output_batch] * len(input_batch) + if isinstance(input_batch, list) + else [output_batch] + ) + # To align with batching, if batch size is 1, wrap the output with a list: + if isinstance(output_batch, dict): + output_batch = [output_batch] + # If a batch processor is given, process the batch: + if batch_processor: + # Process it directly: + batch_processor.process_batch(batch=output_batch) + batch_processor.do_tasks() + elif batches_queue: + # Otherwise, queue the batch: + batches_queue.put(output_batch) + else: + # Otherwise, collect the output as is without processing: + outputs.append(output_batch) + + # Check if given a multiprocessing queue or a batch processor: + if batches_queue: + batches_queue.put(_MULTIPROCESSING_STOP_MARK) + + return outputs if not batch_processor else None + + +#: The value to send into multiprocessing queues to stop the process: +_MULTIPROCESSING_STOP_MARK = "STOP" + + +def _multiprocessing_process_batches( + batch_processor: BatchProcessor, + batches_queue: Queue, + tasks_queue: Queue, + n_task_completers: int, +): + """ + Process the batches in the given batches queue and put the tasks in the given tasks queue. The function will stop + when the given batches queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param batch_processor: A batch processor to process the batches. + :param batches_queue: A queue to get the batches from. + :param tasks_queue: A queue to put the tasks in. + :param n_task_completers: The number of task completers (processes that run the `_multiprocessing_complete_tasks` + function). A stop mark will be sent to the tasks queue for each task completer. + """ + while True: + # Get the batch: + batch: List[dict] = batches_queue.get() + if batch == _MULTIPROCESSING_STOP_MARK: + break + + # Process the batch: + batch_processor.process_batch(batch=batch) + + # Get the tasks: + tasks = batch_processor.get_tasks() + + # Queue the tasks: + for task in tasks: + tasks_queue.put(task.to_tuple()) + + # Mark the end of the batches: + for _ in range(n_task_completers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + +def _multiprocessing_complete_tasks(tasks_queue: Queue, results_queue: Queue): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + tasks_map = { + BaseTask.__name__: BaseTask, + SpeechDiarizationTask.__name__: SpeechDiarizationTask, + SpeechDiarizationPerChannelTask.__name__: SpeechDiarizationPerChannelTask, + } + + while True: + # Get the task: + task = tasks_queue.get() + if task == _MULTIPROCESSING_STOP_MARK: + break + + # Reconstruct the task: + task_class, task_kwargs = task + task = tasks_map[task_class](**task_kwargs) + + # Complete the task: + task.do_task() + results_queue.put((task.is_failed(), task.get_result())) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +# Get the global logger: +_LOGGER = logging.getLogger() + + +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_audio_files( + data_path=Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Save the output directory of this worker: + output_directory = Path(output[0]) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + + # Join the data from all workers: + if rank == 0: + context.logger.info("Collecting data from workers to root worker.") + + # Check if there are different output directories: + output_directories = set([Path(out_dir) for out_dir, _, _ in output]) + for r in range(1, size): + # True means the other workers should pass their files to the root worker (rank 0): + comm.send(len(output_directories) != 1, dest=r) + + # If there are different output directories, listen to the other workers: + if len(output_directories) != 1: + # Collect the files from the other workers: + files = [] + for r in range(1, size): + files.extend(comm.recv(source=r)) + # Write the files to the root worker's output directory: + for file_name, file_content in files: + with open(output_directory / file_name, "w") as f: + f.write(file_content) + + # Concatenate the dataframes: + dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0) + + # Concatenate the errors dictionaries: + errors_dictionary = reduce( + operator.ior, [err for _, _, err in output], {} + ) + + return str(output_directory), dataframe, errors_dictionary + + # Listen to rank 0 to see if there are different output directories and this rank need to send its files to + # it: + if comm.recv(source=0): + files = [] + for file in os.listdir(output_directory): + with open(output_directory / file, "r") as f: + files.append((file, f.read())) + comm.send(files, dest=0) + return None + + return wrapper + + return decorator + + +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]: + is_mpi = False + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + is_mpi = context.labels.get("kind", "job") == "mpijob" + + if is_mpi: + try: + from mpi4py import MPI + + return context, MPI.COMM_WORLD + except ModuleNotFoundError as mpi4py_not_found: + context.logger.error( + "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your " + "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi." + ) + raise mpi4py_not_found + else: + return context, None + except ModuleNotFoundError as module_not_found: + if is_mpi: + raise module_not_found + return None, None + + +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def transcribe( + # Input / Output kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + output_directory: str = None, + # Model loading kwargs: + model_name: str = "openai/whisper-tiny", + device: str = None, + use_flash_attention_2: bool = None, + use_better_transformers: bool = None, + # Generation kwargs: + assistant_model: str = None, + max_new_tokens: int = 128, + chunk_length_s: int = 30, + batch_size: int = 8, + spoken_language: str = None, + translate_to_english: bool = False, + # Diarization kwargs: + speech_diarization: Dict[str, List[Tuple[float, float, str]]] = None, + speech_diarize_per_channel: int = None, + speaker_labels: List[str] = None, + # Other kwargs: + use_multiprocessing: Union[bool, int] = False, + verbose: bool = False, +): + """ + Transcribe audio files into text files and collect additional data. The end result is a directory of transcribed + text files and a dataframe containing the following columns: + + * audio_file - The audio file path. + * transcription_file - The transcribed text file name in the output directory. + + The transcription is based on Huggingface's ASR pipeline - + https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline and + is tested with OpenAI's Whisper models - https://huggingface.co/openai. + + If one of the speaker diarization parameters are given (either `speech_diarization` or + `speech_diarize_per_channel`), the transcription will be written in a conversation format, where each speaker will + be written in a separate line:: + + speaker_1: text + speaker_2: text + speaker_1: text + ... + + :param data_path: A directory of audio files or a single file or a list of files to transcribe. + :param output_directory: Path to a directory to save all transcribed audio files. If not given, will save + the transcribed files in a temporary directory. + :param model_name: The model name to use. Should be a model from the OpenAI's Whisper models for + best results (for example "tiny", "base", "large", etc.). See here for more + information: https://huggingface.co/openai?search_models=whisper. + :param device: The device to use for inference. If not given, will use GPU if available. + :param use_flash_attention_2: Whether to use the Flash Attention 2 implementation. It can be used only with + one of the following GPUs: Nvidia H series and Nvidia A series. T4 support + will be available soon. + + Note: If both `use_flash_attention_2` and + `use_better_transformers` are `None`, the optimization will be chosen + automatically according to the available resources. + + :param use_better_transformers: Whether to use the Better Transformers library to further optimize the model. + Should be used for all use cases that do not support flash attention 2. + + Note: If both `use_flash_attention_2` and `use_better_transformers` are + `None`, the optimization will be chosen automatically according to the + available resources. + :param assistant_model: The assistant model name to use for inference. Notice that the optimizations + (flash attention 2 and better transformers) will be applied for the assistant as + well. Should be a model from Huggingface's distil-whisper (see here for more + information: https://github.com/huggingface/distil-whisper). + + Note: Currently an assistant model is only usable with batch size of 1. + :param max_new_tokens: The maximum number of new tokens to generate. This is used to limit the + generation length. Default is 128 tokens. + :param chunk_length_s: The audio chunk to split the audio to (in seconds). Default is 30 seconds. + :param batch_size: The batch size to use for inference. Default is 2. + :param spoken_language: Aim whisper to know what language is spoken. If None, it will try to detect + it. + :param translate_to_english: Whether to translate the transcriptions to English. + :param speech_diarization: A speech diarization dictionary with the file names to transcribe as keys and + their diarization as value. The diarization is a list of tuples: + (start, end, speaker). An example + for a diarization dictionary:: + + { + "audio_file_name": [ + { + "start": 0.0, + "end": 2.0, + "speaker": "Agent", + }, + { + "start": 2.0, + "end": 4.0, + "speaker": "Client", + }, + ... + ], + ... + } + + Note: The diarization must be for the entire duration of the audio file (as long + as Whisper is predicting words up until then. + :param speech_diarize_per_channel: Perform speech diarization per channel. Each speaker is expected to belong to + a separate channel in the audio. Notice: This will make the transcription + slower as each channel wil be transcribed separatly. If a speech diarization + is passed (via the `speech_diarization` parameter), this parameter is + ignored. + :param speaker_labels: A list of speaker labels by channel order to use for writing the + transcription with respect to per channel speech diarization. This won't be + used together with a given speech diarization (via the `speech_diarization` + parameter). + :param use_multiprocessing: Whether to use multiprocessing to transcribe the audio files. Can be either a + boolean value or an integer. If `True`, will use the default amount of workers + (3): 1 for transcription, 1 for batch processing and 1 for task completion (such + as speech diarization and writing to files). To control the amount of tasks + completion workers, an integer can be provided to specify the amount of workers. + `False`, will use a single process. Default is `False`. + :param verbose: Whether to print the progress of the transcription. Default is `False`. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Get the output directory: + if output_directory is None: + if verbose: + _LOGGER.info("No output directory given, using temporary directory.") + output_directory = tempfile.mkdtemp() + output_directory = Path(output_directory).absolute() + output_directory.mkdir(exist_ok=True, parents=True) + if verbose: + _LOGGER.info(f"Transcriptions will be saved to: {output_directory}") + + # Initialize a batch processor according to user requirements (no speech diarization, given speech diarization, + # speech diarization per channel): + if speech_diarization: + batch_processor = SpeechDiarizationBatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + speech_diarization=speech_diarization, + ) + elif speech_diarize_per_channel: + batch_processor = PerChannelSpeechDiarizationBatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + n_channels=speech_diarize_per_channel, + speakers=speaker_labels, + ) + else: + batch_processor = BatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + ) + + # Initialize the transcription pipeline: + transcriber = Transcriber( + device=device, + use_flash_attention_2=use_flash_attention_2, + use_better_transformers=use_better_transformers, + assistant_model=assistant_model, + model_name=model_name, + max_new_tokens=max_new_tokens, + chunk_length_s=chunk_length_s, + batch_size=batch_size, + return_timestamps=( + "word" + if speech_diarization is not None or speech_diarize_per_channel is not None + else False + ), + per_channel_transcription=speech_diarize_per_channel or 0, + spoken_language=spoken_language, + translate_to_english=translate_to_english, + ) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing + if isinstance(use_multiprocessing, int) + else 1, + audio_files=audio_files, + batch_processor=batch_processor, + transcriber=transcriber, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + batch_processor=batch_processor, + transcriber=transcriber, + verbose=verbose, + ) + + # Process the results: + if verbose: + _LOGGER.info("Summarizing the results.") + successes = [] + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes.append(result) + successes = pd.DataFrame(successes, columns=["audio_file", "transcription_file"]) + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(audio_files)})\n" + f"Transcriptions summary:\n" + f"{successes.head()}" + ) + + return str(output_directory), successes, errors + + +def _get_audio_files( + data_path: Union[Path, str, list], +) -> List[Path]: + """ + Get the audio files to transcribe. If a path to a directory is given, all files in the directory will be collected. + + :param data_path: The data path to collect the audio files from. + + :returns: The audio files list. + """ + # Check if given a list of paths: + if isinstance(data_path, list): + audio_files = [] + for path in data_path: + audio_files.extend(_get_audio_files(data_path=path)) + return audio_files + + # Check if given a single string path to cast it to a `pathlib.Path`: + if isinstance(data_path, str): + data_path = Path(data_path).absolute() + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a " + f"file. Given: {str(data_path)} " + ) + + return audio_files + + +def _run( + audio_files: List[Path], + batch_processor: BatchProcessor, + transcriber: Transcriber, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run the transcription without multiprocessing. + + :param audio_files: The audio files to transcribe. + :param batch_processor: The batch processor to use. + :param transcriber: The transcriber to use. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the transcription pipeline: + if verbose: + _LOGGER.info(f"Loading the transcription pipeline.") + transcriber.load() + if verbose: + _LOGGER.info("Transcription pipeline loaded.") + + # Transcribe the files: + transcriber.transcribe( + audio_files=audio_files, + batch_processor=batch_processor, + verbose=verbose, + ) + + # Return the results: + return batch_processor.get_results() + + +def _parallel_run( + n_workers: int, + audio_files: List[Path], + batch_processor: BatchProcessor, + transcriber: Transcriber, + verbose: bool, +): + """ + Run the transcription with multiprocessing. + + :param n_workers: The amount of workers to use as task completers. + :param audio_files: The audio files to transcribe. + :param batch_processor: The batch processor to use. + :param transcriber: The transcriber to use. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Initialize the multiprocessing queues: + batches_queue = Queue() + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + batch_processing_process = Process( + target=_multiprocessing_process_batches, + kwargs={ + "batch_processor": batch_processor, + "batches_queue": batches_queue, + "tasks_queue": tasks_queue, + "n_task_completers": n_workers, + }, + ) + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={"tasks_queue": tasks_queue, "results_queue": results_queue}, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + batch_processing_process.start() + for p in task_completion_processes: + p.start() + + # Load the transcription pipeline: + if verbose: + _LOGGER.info(f"Loading the transcription pipeline.") + transcriber.load() + if verbose: + _LOGGER.info("Transcription pipeline loaded.") + + # Transcribe the files: + transcriber.transcribe( + audio_files=audio_files, batches_queue=batches_queue, verbose=verbose + ) + + # Collect the results: + results = [] + stop_marks_counter = 0 + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, str]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + + # Wait for the processes to finish: + results_queue.empty() + batch_processing_process.join() + for p in task_completion_processes: + p.join() + + return results \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/documentation.html b/functions/master/transcribe/1.2.0/static/documentation.html new file mode 100644 index 00000000..d92df103 --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/documentation.html @@ -0,0 +1,627 @@ + + + + + + + +transcribe package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    transcribe package#

    +
    +

    Submodules#

    +
    +
    +

    transcribe.transcribe module#

    +
    +
    +class transcribe.transcribe.BaseTask(audio_file: Path, transcription_output: dict | str, text_file: Path)[source]#
    +

    Bases: object

    +

    A task to write the transcription to file.

    +
    +
    +do_task()[source]#
    +

    Try to perform the task storing an error if occurred.

    +
    +
    +
    +get_result() Tuple[str, str][source]#
    +

    Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the +text file name.

    +
    +
    Returns:
    +

    The task’s result.

    +
    +
    +
    +
    +
    +is_failed() bool[source]#
    +

    Check if the task failed.

    +
    +
    Returns:
    +

    Whether the task failed.

    +
    +
    +
    +
    +
    +to_tuple() Tuple[str, dict][source]#
    +

    Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).

    +
    +
    Returns:
    +

    The converted task.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.BatchProcessor(audio_files: List[Path], output_directory: Path)[source]#
    +

    Bases: object

    +

    A batch processor to process batches of transcriptions. The batch processor is creating tasks and is aimed to be +working along the transcriber. It can be used with multiprocessing queue or run the tasks directly using the +associated methods.

    +
    +
    +do_tasks()[source]#
    +

    Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber.

    +
    +
    +
    +get_results() List[Tuple[bool, Tuple[str, str]]][source]#
    +

    Get the results of the tasks. The stored results are then cleared.

    +
    +
    Returns:
    +

    The results of the tasks.

    +
    +
    +
    +
    +
    +get_tasks() List[BaseTask][source]#
    +

    Get the tasks to perform.

    +
    +
    Returns:
    +

    The tasks to perform.

    +
    +
    +
    +
    +
    +process_batch(batch: List[dict | str])[source]#
    +

    Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch +processor.

    +
    +
    Parameters:
    +

    batch – The batch of transcriptions to process.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.PerChannelSpeechDiarizationBatchProcessor(audio_files: List[Path], output_directory: Path, n_channels: int, speakers: List[str])[source]#
    +

    Bases: BatchProcessor

    +

    A batch processor to process batches of transcriptions per channel. The batch processor is creating tasks with the +selected amount of channels given and is aimed to be working along the transcriber. It can be used with +multiprocessing queue or run the tasks directly using the associated methods.

    +
    +
    +process_batch(batch: List[dict])[source]#
    +

    Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch +processor.

    +
    +
    Parameters:
    +

    batch – The batch of transcriptions to process.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.SpeechDiarizationBatchProcessor(audio_files: List[Path], output_directory: Path, speech_diarization: dict)[source]#
    +

    Bases: BatchProcessor

    +

    A batch processor to process batches of transcriptions with respect to a given speech diarization. The batch +processor is creating tasks and is aimed to be working along the transcriber. It can be used with multiprocessing +queue or run the tasks directly using the associated methods.

    +
    +
    +process_batch(batch: List[dict])[source]#
    +

    Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch +processor.

    +
    +
    Parameters:
    +

    batch – The batch of transcriptions to process.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.SpeechDiarizationPerChannelTask(audio_file: Path, text_file: Path)[source]#
    +

    Bases: BaseTask

    +

    A task to write the transcription to file with respect to a given speech diarization per channel.

    +
    +
    +do_task()[source]#
    +

    Try to perform the task storing an error if occurred.

    +
    +
    +
    +to_tuple() Tuple[str, dict][source]#
    +

    Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).

    +
    +
    Returns:
    +

    The converted task.

    +
    +
    +
    +
    +
    +property transcription_output_channels: List[Tuple[str, dict]]#
    +

    Get the transcription output channels.

    +
    +
    Returns:
    +

    The transcription output channels.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.SpeechDiarizationTask(audio_file: Path, transcription_output: dict, text_file: Path, speech_diarization: List[Tuple[float, float, str]])[source]#
    +

    Bases: BaseTask

    +

    A task to write the transcription to file with respect to a given speech diarization.

    +
    +
    +to_tuple() Tuple[str, dict][source]#
    +

    Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).

    +
    +
    Returns:
    +

    The converted task.

    +
    +
    +
    +
    +
    +
    +class transcribe.transcribe.Transcriber(model_name: str, device: str | None = None, use_flash_attention_2: bool | None = None, use_better_transformers: bool | None = None, assistant_model: str | None = None, max_new_tokens: int = 128, chunk_length_s: int = 30, batch_size: int = 2, spoken_language: str | None = None, translate_to_english: bool = False, return_timestamps: bool | Literal['word'] = False, per_channel_transcription: int = 0)[source]#
    +

    Bases: object

    +

    A transcription wrapper for the Huggingface’s ASR pipeline - +https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline to +use with OpenAI’s Whisper models - https://huggingface.co/openai.

    +
    +
    +load()[source]#
    +

    Load the transcriber. Must be called before transcribing.

    +
    +
    +
    +transcribe(audio_files: List[Path], batch_processor: BatchProcessor | None = None, batches_queue: Queue | None = None, verbose: bool = False) List[List[dict]] | None[source]#
    +

    Transcribe the given audio files. The transcriptions will be sent to a queue or a batch processor for further +processing like writing to text files. If no queue or batch processor is given, the transcriptions outputs from +the pipeline will be returned. Otherwise, None is returned.

    +
    +
    Parameters:
    +
      +
    • audio_files – The audio files to transcribe.

    • +
    • batch_processor – A batch processor.

    • +
    • batches_queue – A multiprocessing queue to put the batches in.

    • +
    • verbose – Whether to show a progress bar. Default is False.

    • +
    +
    +
    Returns:
    +

    The transcriptions outputs from the pipeline if no queue or batch processor is given, otherwise, +None.

    +
    +
    +
    +
    +
    +
    +transcribe.transcribe.open_mpi_handler(worker_inputs: List[str], root_worker_inputs: Dict[str, Any] | None = None)[source]#
    +
    +
    +
    +transcribe.transcribe.transcribe(data_path: str | Path | List[str | Path], output_directory: str | None = None, model_name: str = 'openai/whisper-tiny', device: str | None = None, use_flash_attention_2: bool | None = None, use_better_transformers: bool | None = None, assistant_model: str | None = None, max_new_tokens: int = 128, chunk_length_s: int = 30, batch_size: int = 8, spoken_language: str | None = None, translate_to_english: bool = False, speech_diarization: Dict[str, List[Tuple[float, float, str]]] | None = None, speech_diarize_per_channel: int | None = None, speaker_labels: List[str] | None = None, use_multiprocessing: bool | int = False, verbose: bool = False)[source]#
    +

    Transcribe audio files into text files and collect additional data. The end result is a directory of transcribed +text files and a dataframe containing the following columns:

    +
      +
    • audio_file - The audio file path.

    • +
    • transcription_file - The transcribed text file name in the output directory.

    • +
    +

    The transcription is based on Huggingface’s ASR pipeline - +https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline and +is tested with OpenAI’s Whisper models - https://huggingface.co/openai.

    +

    If one of the speaker diarization parameters are given (either speech_diarization or +speech_diarize_per_channel), the transcription will be written in a conversation format, where each speaker will +be written in a separate line:

    +
    speaker_1: text
    +speaker_2: text
    +speaker_1: text
    +...
    +
    +
    +
    +
    Parameters:
    +
      +
    • data_path – A directory of audio files or a single file or a list of files to transcribe.

    • +
    • output_directory – Path to a directory to save all transcribed audio files. If not given, will save +the transcribed files in a temporary directory.

    • +
    • model_name – The model name to use. Should be a model from the OpenAI’s Whisper models for +best results (for example “tiny”, “base”, “large”, etc.). See here for more +information: https://huggingface.co/openai?search_models=whisper.

    • +
    • device – The device to use for inference. If not given, will use GPU if available.

    • +
    • use_flash_attention_2

      Whether to use the Flash Attention 2 implementation. It can be used only with +one of the following GPUs: Nvidia H series and Nvidia A series. T4 support +will be available soon.

      +

      Note: If both use_flash_attention_2 and +use_better_transformers are None, the optimization will be chosen +automatically according to the available resources.

      +

    • +
    • use_better_transformers

      Whether to use the Better Transformers library to further optimize the model. +Should be used for all use cases that do not support flash attention 2.

      +

      Note: If both use_flash_attention_2 and use_better_transformers are +None, the optimization will be chosen automatically according to the +available resources.

      +

    • +
    • assistant_model

      The assistant model name to use for inference. Notice that the optimizations +(flash attention 2 and better transformers) will be applied for the assistant as +well. Should be a model from Huggingface’s distil-whisper (see here for more +information: huggingface/distil-whisper).

      +

      Note: Currently an assistant model is only usable with batch size of 1.

      +

    • +
    • max_new_tokens – The maximum number of new tokens to generate. This is used to limit the +generation length. Default is 128 tokens.

    • +
    • chunk_length_s – The audio chunk to split the audio to (in seconds). Default is 30 seconds.

    • +
    • batch_size – The batch size to use for inference. Default is 2.

    • +
    • spoken_language – Aim whisper to know what language is spoken. If None, it will try to detect +it.

    • +
    • translate_to_english – Whether to translate the transcriptions to English.

    • +
    • speech_diarization

      A speech diarization dictionary with the file names to transcribe as keys and +their diarization as value. The diarization is a list of tuples: +(start, end, speaker). An example +for a diarization dictionary:

      +
      {
      +
      +
      +
      +
      +
      ”audio_file_name”: [
      +
      {

      “start”: 0.0, +“end”: 2.0, +“speaker”: “Agent”,

      +
      +
      +

      }, +{

      +
      +

      ”start”: 2.0, +“end”: 4.0, +“speaker”: “Client”,

      +
      +
      +
      +
      +

      }

      +

      Note: The diarization must be for the entire duration of the audio file (as long +as Whisper is predicting words up until then.

      +

    • +
    • speech_diarize_per_channel – Perform speech diarization per channel. Each speaker is expected to belong to +a separate channel in the audio. Notice: This will make the transcription +slower as each channel wil be transcribed separatly. If a speech diarization +is passed (via the speech_diarization parameter), this parameter is +ignored.

    • +
    • speaker_labels – A list of speaker labels by channel order to use for writing the +transcription with respect to per channel speech diarization. This won’t be +used together with a given speech diarization (via the speech_diarization +parameter).

    • +
    • use_multiprocessing – Whether to use multiprocessing to transcribe the audio files. Can be either a +boolean value or an integer. If True, will use the default amount of workers +(3): 1 for transcription, 1 for batch processing and 1 for task completion (such +as speech diarization and writing to files). To control the amount of tasks +completion workers, an integer can be provided to specify the amount of workers. +False, will use a single process. Default is False.

    • +
    • verbose – Whether to print the progress of the transcription. Default is False.

    • +
    +
    +
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/example.html b/functions/master/transcribe/1.2.0/static/example.html new file mode 100644 index 00000000..261d8df0 --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/example.html @@ -0,0 +1,605 @@ + + + + + + + +Transcribe tutorial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    Transcribe tutorial

    + +
    + +
    +
    + +
    +
    +

    Transcribe tutorial#

    +
    +
    +
    import tempfile
    +import mlrun
    +
    +
    +
    +
    +
    +

    Importing the transcribe function from hub#

    +

    To import the function directly from hub, use:

    +
    transcribe_fn = mlrun.import_function("hub://transcribe")
    +
    +
    +
    +
    +
    artifact_path = tempfile.mkdtemp()
    +
    +
    +
    +
    +
    +
    +
    transcribe_fn = mlrun.import_function("function.yaml")
    +
    +
    +
    +
    +
    +
    +

    Running transcribe#

    +
    +
    +
    transcribe_run = transcribe_fn.run(
    +    handler="transcribe",
    +    params={
    +        "model_name": "tiny",
    +        "input_path": "./data",
    +        "decoding_options": {"fp16": False},
    +        "output_directory": "./output",
    +    },
    +    returns=[
    +        "transcriptions: path",
    +        "transcriptions_df: dataset",
    +        {"key": "transcriptions_errors", "artifact_type": "file", "file_format": "yaml"},
    +    ],
    +    local=True,
    +    artifact_path=artifact_path,
    +)
    +
    +
    +
    +
    +
    > 2023-07-16 17:14:01,968 [info] Storing function: {'name': 'transcribe-transcribe', 'uid': 'd1384cb679bc4c178b0195d964b628a8', 'db': None}
    +> 2023-07-16 17:14:01,969 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:01,969 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:01,970 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:01,970 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:01,972 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:01,972 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:09,804 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:09,805 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:09,805 [info] Loading whisper model: 'tiny'
    +
    +
    +
    The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
    +IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
    +
    +
    +
    > 2023-07-16 17:14:10,374 [info] Model loaded.
    +
    +
    +
    Transcribing:  67%|██████▋   | 2/3 [00:02<00:01,  1.04s/file]
    +
    +
    +
    > 2023-07-16 17:14:12,556 [warning] Error in file: '/Users/Yonatan_Shelach/projects/functions/transcribe/data/error_file.txt'
    +
    +
    +
    Transcribing: 100%|██████████| 3/3 [00:02<00:00,  1.39file/s]
    +
    +
    +
    > 2023-07-16 17:14:12,566 [info] Done:
    +      audio_file transcription_file language     length  rate_of_speech
    +0  speech_01.mp3      speech_01.txt       en   2.011333        3.480278
    +1  speech_02.mp3      speech_02.txt       en  20.793500        2.548873
    +> 2023-07-16 17:14:12,596 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:12,597 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:12,659 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:12,660 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:12,671 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:12,672 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +
    +
    +
    
    +
    +
    +
    > 2023-07-16 17:14:12,707 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:12,707 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +> 2023-07-16 17:14:12,708 [warning] Could not detect path to API server, not connected to API server!
    +> 2023-07-16 17:14:12,708 [warning] MLRUN_DBPATH is not set. Set this environment variable to the URL of the API server in order to connect
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    default
    ...b628a8
    0Jul 16 14:14:01completedtranscribe-transcribe
    kind=
    owner=Yonatan_Shelach
    host=M-QWXQJK77Q0
    model_name=tiny
    audio_files_directory=./data
    decoding_options={'fp16': False}
    output_directory=./output
    transcriptions
    transcriptions_df
    transcriptions_errors
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods
    > 2023-07-16 17:14:12,721 [info] Run execution finished: {'status': 'completed', 'name': 'transcribe-transcribe'}
    +
    +
    +
    +
    +
    +
    +
    transcribe_run.outputs
    +
    +
    +
    +
    +
    {'transcriptions': 'store://artifacts/default/transcribe-transcribe_transcriptions:d1384cb679bc4c178b0195d964b628a8',
    + 'transcriptions_df': 'store://artifacts/default/transcribe-transcribe_transcriptions_df:d1384cb679bc4c178b0195d964b628a8',
    + 'transcriptions_errors': 'store://artifacts/default/transcribe-transcribe_transcriptions_errors:d1384cb679bc4c178b0195d964b628a8'}
    +
    +
    +
    +
    +

    Notice: If connected to mlrun server, you can simply use:

    +
    df = transcribe_run.artifact("transcriptions_df")
    +
    +
    +
    +
    +
    artifact_path += f"/{transcribe_run.metadata.name}/{transcribe_run.metadata.iteration}/"
    +
    +
    +
    +
    +
    +
    +
    df = mlrun.get_dataitem(artifact_path + "transcriptions_df.parquet").as_df()
    +
    +
    +
    +
    +
    +
    +
    df.head()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    audio_filetranscription_filelanguagelengthrate_of_speech
    0speech_01.mp3speech_01.txten2.0113333.480278
    1speech_02.mp3speech_02.txten20.7935002.548873
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/function.html b/functions/master/transcribe/1.2.0/static/function.html new file mode 100644 index 00000000..1f55b3ab --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/function.html @@ -0,0 +1,320 @@ + + + + + + + + + + + Source + + + + +
    +        
    +kind: job
    +metadata:
    +  categories:
    +  - audio
    +  - genai
    +  tag: ''
    +  name: transcribe
    +verbose: false
    +spec:
    +  build:
    +    origin_filename: ''
    +    requirements:
    +    - transformers
    +    - tqdm
    +    - torchaudio
    +    - torch
    +    - accelerate
    +    base_image: mlrun/mlrun
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz
    +  disable_auto_mount: false
    +  description: Transcribe audio files into text files
    +  image: ''
    +  command: ''
    +  default_handler: transcribe
    +  entry_points:
    +    do_task:
    +      name: do_task
    +      doc: Try to perform the task storing an error if occurred.
    +      lineno: 348
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +    is_failed:
    +      name: is_failed
    +      doc: Check if the task failed.
    +      lineno: 70
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: Whether the task failed.
    +        type: bool
    +    get_result:
    +      name: get_result
    +      doc: 'Get the result of the task. If the task failed, the error will be returned,
    +        otherwise, the result will be the
    +
    +        text file name.'
    +      lineno: 78
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: The task's result.
    +        type: Tuple[str, str]
    +    to_tuple:
    +      name: to_tuple
    +      doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing
    +        to pass in queue).
    +      lineno: 358
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: The converted task.
    +        type: Tuple[str, dict]
    +    transcription_output_channels:
    +      name: transcription_output_channels
    +      doc: Get the transcription output channels.
    +      lineno: 340
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: The transcription output channels.
    +        type: List[Tuple[str, dict]]
    +    process_batch:
    +      name: process_batch
    +      doc: 'Process a batch of transcriptions. Tasks related to the given batch will
    +        be created and stored in the batch
    +
    +        processor.'
    +      lineno: 575
    +      parameters:
    +      - name: self
    +      - name: batch
    +        type: List[dict]
    +        doc: The batch of transcriptions to process.
    +      has_varargs: false
    +      has_kwargs: false
    +    get_tasks:
    +      name: get_tasks
    +      doc: Get the tasks to perform.
    +      lineno: 453
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: The tasks to perform.
    +        type: List[BaseTask]
    +    do_tasks:
    +      name: do_tasks
    +      doc: Perform the tasks. Should be used if no multiprocessing queue is given
    +        to a transcriber.
    +      lineno: 463
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +    get_results:
    +      name: get_results
    +      doc: Get the results of the tasks. The stored results are then cleared.
    +      lineno: 471
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - doc: The results of the tasks.
    +        type: List[Tuple[bool, Tuple[str, str]]]
    +    load:
    +      name: load
    +      doc: Load the transcriber. Must be called before transcribing.
    +      lineno: 695
    +      parameters:
    +      - name: self
    +      has_varargs: false
    +      has_kwargs: false
    +    transcribe:
    +      name: transcribe
    +      doc: "Transcribe audio files into text files and collect additional data. The\
    +        \ end result is a directory of transcribed\ntext files and a dataframe containing\
    +        \ the following columns:\n\n* audio_file - The audio file path.\n* transcription_file\
    +        \ - The transcribed text file name in the output directory.\n\nThe transcription\
    +        \ is based on Huggingface's ASR pipeline -\nhttps://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline\
    +        \ and\nis tested with OpenAI's Whisper models - https://huggingface.co/openai.\n\
    +        \nIf one of the speaker diarization parameters are given (either `speech_diarization`\
    +        \ or\n`speech_diarize_per_channel`), the transcription will be written in\
    +        \ a conversation format, where each speaker will\nbe written in a separate\
    +        \ line::\n\n    speaker_1: text\n    speaker_2: text\n    speaker_1: text\n\
    +        \    ..."
    +      lineno: 1097
    +      parameters:
    +      - name: data_path
    +        type: Union[str, Path, List[Union[str, Path]]]
    +        doc: A directory of audio files or a single file or a list of files to transcribe.
    +      - name: output_directory
    +        type: str
    +        doc: Path to a directory to save all transcribed audio files. If not given,
    +          will save the transcribed files in a temporary directory.
    +        default: null
    +      - name: model_name
    +        type: str
    +        doc: 'The model name to use. Should be a model from the OpenAI''s Whisper
    +          models for best results (for example "tiny", "base", "large", etc.). See
    +          here for more information: https://huggingface.co/openai?search_models=whisper.'
    +        default: openai/whisper-tiny
    +      - name: device
    +        type: str
    +        doc: The device to use for inference. If not given, will use GPU if available.
    +        default: null
    +      - name: use_flash_attention_2
    +        type: bool
    +        doc: 'Whether to use the Flash Attention 2 implementation. It can be used
    +          only with one of the following GPUs: Nvidia H series and Nvidia A series.
    +          T4 support will be available soon.'
    +        default: null
    +      - name: use_better_transformers
    +        type: bool
    +        doc: Whether to use the Better Transformers library to further optimize the
    +          model. Should be used for all use cases that do not support flash attention
    +          2.
    +        default: null
    +      - name: assistant_model
    +        type: str
    +        doc: 'The assistant model name to use for inference. Notice that the optimizations
    +          (flash attention 2 and better transformers) will be applied for the assistant
    +          as well. Should be a model from Huggingface''s distil-whisper (see here
    +          for more information: https://github.com/huggingface/distil-whisper).'
    +        default: null
    +      - name: max_new_tokens
    +        type: int
    +        doc: The maximum number of new tokens to generate. This is used to limit the
    +          generation length. Default is 128 tokens.
    +        default: 128
    +      - name: chunk_length_s
    +        type: int
    +        doc: The audio chunk to split the audio to (in seconds). Default is 30 seconds.
    +        default: 30
    +      - name: batch_size
    +        type: int
    +        doc: The batch size to use for inference. Default is 2.
    +        default: 8
    +      - name: spoken_language
    +        type: str
    +        doc: Aim whisper to know what language is spoken. If None, it will try to
    +          detect it.
    +        default: null
    +      - name: translate_to_english
    +        type: bool
    +        doc: Whether to translate the transcriptions to English.
    +        default: false
    +      - name: speech_diarization
    +        type: Dict[str, List[Tuple[float, float, str]]]
    +        doc: 'A speech diarization dictionary with the file names to transcribe as
    +          keys and their diarization as value. The diarization is a list of tuples:
    +          (start, end, speaker). An example for a diarization dictionary::'
    +        default: null
    +      - name: speech_diarize_per_channel
    +        type: int
    +        doc: 'Perform speech diarization per channel. Each speaker is expected to
    +          belong to a separate channel in the audio. Notice: This will make the transcription
    +          slower as each channel wil be transcribed separatly. If a speech diarization
    +          is passed (via the `speech_diarization` parameter), this parameter is ignored.'
    +        default: null
    +      - name: speaker_labels
    +        type: List[str]
    +        doc: A list of speaker labels by channel order to use for writing the transcription
    +          with respect to per channel speech diarization. This won't be used together
    +          with a given speech diarization (via the `speech_diarization` parameter).
    +        default: null
    +      - name: use_multiprocessing
    +        type: Union[bool, int]
    +        doc: 'Whether to use multiprocessing to transcribe the audio files. Can be
    +          either a boolean value or an integer. If `True`, will use the default amount
    +          of workers (3): 1 for transcription, 1 for batch processing and 1 for task
    +          completion (such as speech diarization and writing to files). To control
    +          the amount of tasks completion workers, an integer can be provided to specify
    +          the amount of workers. `False`, will use a single process. Default is `False`.'
    +        default: false
    +      - name: verbose
    +        type: bool
    +        doc: Whether to print the progress of the transcription. Default is `False`.
    +        default: false
    +      has_varargs: false
    +      has_kwargs: false
    +    audio_iterator:
    +      name: audio_iterator
    +      doc: ''
    +      lineno: 804
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - type: Generator[Union[dict, str], None, None]
    +    batch_iterator:
    +      name: batch_iterator
    +      doc: ''
    +      lineno: 816
    +      has_varargs: false
    +      has_kwargs: false
    +      outputs:
    +      - type: Generator[List[Union[dict, str]], None, None]
    +    open_mpi_handler:
    +      name: open_mpi_handler
    +      doc: ''
    +      lineno: 957
    +      parameters:
    +      - name: worker_inputs
    +        type: List[str]
    +      - name: root_worker_inputs
    +        type: Dict[str, Any]
    +        default: null
    +      has_varargs: false
    +      has_kwargs: false
    +    decorator:
    +      name: decorator
    +      doc: ''
    +      lineno: 969
    +      parameters:
    +      - name: handler
    +      has_varargs: false
    +      has_kwargs: false
    +    wrapper:
    +      name: wrapper
    +      doc: ''
    +      lineno: 974
    +      has_varargs: false
    +      has_kwargs: true
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/item.html b/functions/master/transcribe/1.2.0/static/item.html new file mode 100644 index 00000000..b39372bd --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/item.html @@ -0,0 +1,64 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- audio
    +- genai
    +description: Transcribe audio files into text files
    +doc: ''
    +example: transcribe.ipynb
    +generationDate: 2023-07-13:11-20
    +hidden: false
    +icon: ''
    +labels:
    +  author: yonatans
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: transcribe
    +platformVersion: 3.5.3
    +spec:
    +  filename: transcribe.py
    +  handler: transcribe
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +    - transformers
    +    - tqdm
    +    - torchaudio
    +    - torch
    +    - accelerate
    +url: ''
    +version: 1.2.0
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/source.html b/functions/master/transcribe/1.2.0/static/source.html new file mode 100644 index 00000000..3c63a460 --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/source.html @@ -0,0 +1,1498 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2024 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import logging
    +import operator
    +import os
    +import tempfile
    +from functools import reduce, wraps
    +from multiprocessing import Process, Queue
    +from pathlib import Path
    +from typing import Any, Dict, Generator, List, Literal, NamedTuple, Tuple, Union
    +
    +import pandas as pd
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +from transformers import (
    +    AutomaticSpeechRecognitionPipeline,
    +    AutoModelForCausalLM,
    +    pipeline,
    +)
    +from transformers.utils import is_flash_attn_2_available
    +
    +
    +class BaseTask:
    +    """
    +    A task to write the transcription to file.
    +    """
    +
    +    def __init__(
    +        self, audio_file: Path, transcription_output: Union[dict, str], text_file: Path
    +    ):
    +        """
    +        Initialize the task.
    +
    +        :param audio_file:           Path to the audio file that was transcribed.
    +        :param transcription_output: The transcription output from the pipeline. String means an exception was raised.
    +        :param text_file:            Path to the text file to write the transcription to.
    +        """
    +        # Store the parameters:
    +        self._audio_file = audio_file
    +        self._transcription_output = transcription_output
    +        self._text_file = text_file
    +
    +        # Prepare the error variable:
    +        self._error: str = None
    +
    +    def do_task(self):
    +        """
    +        Try to perform the task storing an error if occurred.
    +        """
    +        if isinstance(self._transcription_output, str):
    +            self._error = self._transcription_output
    +            return
    +        try:
    +            self._do_task()
    +        except Exception as exception:
    +            self._error = str(exception)
    +
    +    def is_failed(self) -> bool:
    +        """
    +        Check if the task failed.
    +
    +        :returns: Whether the task failed.
    +        """
    +        return self._error is not None
    +
    +    def get_result(self) -> Tuple[str, str]:
    +        """
    +        Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the
    +        text file name.
    +
    +        :returns: The task's result.
    +        """
    +        if self.is_failed():
    +            return self._audio_file.name, self._error
    +        return self._audio_file.name, self._text_file.name
    +
    +    def to_tuple(self) -> Tuple[str, dict]:
    +        """
    +        Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).
    +
    +        :returns: The converted task.
    +        """
    +        return self.__class__.__name__, {
    +            "audio_file": self._audio_file,
    +            "transcription_output": self._transcription_output,
    +            "text_file": self._text_file,
    +        }
    +
    +    def _do_task(self):
    +        """
    +        Perform the task - write the transcription to the stored file path.
    +        """
    +        # Checking for no duplications:
    +        i = 1
    +        while self._text_file.exists():
    +            i += 1
    +            self._text_file = (
    +                self._text_file.parent
    +                / f"{self._text_file.stem.rsplit('_', 1)[0]}_{i}{self._text_file.suffix}"
    +            )
    +
    +        # Make sure all directories are created:
    +        self._text_file.parent.mkdir(exist_ok=True, parents=True)
    +
    +        # Write to file:
    +        with open(self._text_file, "w") as fp:
    +            fp.write(self._transcription_output["text"])
    +
    +
    +class SpeechDiarizationTask(BaseTask):
    +    """
    +    A task to write the transcription to file with respect to a given speech diarization.
    +    """
    +
    +    class _DiarizationSegment(NamedTuple):
    +        """
    +        A speech diarization segment.
    +        """
    +
    +        start: float
    +        end: float
    +        speaker: str
    +
    +    class _WordTimestamp(NamedTuple):
    +        """
    +        A word with its start and end timestamps.
    +        """
    +
    +        start: float
    +        end: float
    +        text: str
    +
    +    def __init__(
    +        self,
    +        audio_file: Path,
    +        transcription_output: dict,
    +        text_file: Path,
    +        speech_diarization: List[Tuple[float, float, str]],
    +    ):
    +        """
    +        Initialize the task.
    +
    +        :param audio_file:           Path to the audio file that was transcribed.
    +        :param transcription_output: The transcription output from the pipeline.
    +        :param text_file:            Path to the text file to write the transcription to.
    +        :param speech_diarization:   A speech diarization as a list of tuples: (start, end, speaker).
    +        """
    +        super().__init__(
    +            audio_file=audio_file,
    +            transcription_output=transcription_output,
    +            text_file=text_file,
    +        )
    +        self._speech_diarization = speech_diarization
    +        self._segments: List[SpeechDiarizationTask._DiarizationSegment] = None
    +        self._last_chosen_index = 0
    +
    +    def to_tuple(self) -> Tuple[str, dict]:
    +        """
    +        Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).
    +
    +        :returns: The converted task.
    +        """
    +        task_class, task_kwargs = super().to_tuple()
    +        return task_class, {
    +            **task_kwargs,
    +            "speech_diarization": self._speech_diarization,
    +        }
    +
    +    def _do_task(self):
    +        """
    +        Perform the task - write the transcription to the stored file path with respect to the given speech diarization.
    +        """
    +        # Check if a speech diarization is given, if not, just write the transcription to file:
    +        if not self._speech_diarization:
    +            super()._do_task()
    +            return
    +
    +        # Cast the chunks to word timestamps tuples:
    +        words = [
    +            SpeechDiarizationTask._WordTimestamp(
    +                start=chunk["timestamp"][0],
    +                end=chunk["timestamp"][1],
    +                text=chunk["text"],
    +            )
    +            for chunk in self._transcription_output["chunks"]
    +        ]
    +
    +        # Cast speech diarization to segments tuples:
    +        self._segments = [
    +            SpeechDiarizationTask._DiarizationSegment(*segment)
    +            for segment in self._speech_diarization
    +        ]
    +
    +        # Try to match the Whisper model predicted timestamps to the closest diarization segment (closest diarization
    +        # segment will be the most overlapping with the word, and if there is no overlap, the closest segment to the
    +        # word):
    +        speaker = self._segments[self._last_chosen_index].speaker
    +        text = f"{speaker}:"
    +        for word in words:
    +            # Get the next diarization segment:
    +            self._get_next_segment(word=word)
    +            # Check if the segment is of the same speaker:
    +            if self._segments[self._last_chosen_index].speaker == speaker:
    +                # Collect the word:
    +                text += word.text
    +            else:
    +                # Append a newline and update the new speaker:
    +                speaker = self._segments[self._last_chosen_index].speaker
    +                text += f"\n{speaker}:{word.text}"
    +
    +        # Update the transcription output with the new text to write it to file:
    +        self._transcription_output["text"] = text
    +        super()._do_task()
    +
    +    def _get_next_segment(
    +        self,
    +        word: _WordTimestamp,
    +    ):
    +        """
    +        Get the next diarization segment the given word falls into. The `self._last_chosen_index` will be updated
    +        accordingly.
    +
    +        :param word: The word timestamp to match to the next segment.
    +        """
    +        # If the last chosen segment is the last segment, return it:
    +        if self._last_chosen_index == len(self._segments) - 1:
    +            return
    +
    +        # Get the last chosen diarization segment:
    +        last_chosen = self._segments[self._last_chosen_index]
    +
    +        # None value may appear if the word is the last word in the audio file, or it was split during inference. In
    +        # that case, we'll set the last segment:
    +        if word.end is None:
    +            self._last_chosen_index = len(self._segments) - 1
    +            return
    +
    +        # If the word ends before the last chosen segment:
    +        if word.end <= last_chosen.start:
    +            # Then it is still the closest segment
    +            return
    +
    +        # We check if it ends inside the last chosen segment:
    +        if word.end < last_chosen.end:
    +            # Then it still is the closest segment
    +            return
    +
    +        # The word ends after the segment, we need to collect all next segments up until the word ends before them:
    +        possible_segments = [self._last_chosen_index]
    +        for i in range(self._last_chosen_index + 1, len(self._segments)):
    +            if word.end > self._segments[i].end:
    +                possible_segments.append(i)
    +                continue
    +            possible_segments.append(i)
    +            break
    +
    +        # Check for the most overlapping option:
    +        best_overlap = 0
    +        most_overlapping_segment_index = None
    +        for i in possible_segments:
    +            # If the word starts before segment:
    +            if word.start <= self._segments[i].start:
    +                # If it ends before the segment, there is an overlap from the start of the segment to the end of the
    +                # word:
    +                if word.end < self._segments[i].end:
    +                    overlap = word.end - self._segments[i].start
    +                else:
    +                    # The word is wrapping the segment, the overlap is the segment's length:
    +                    overlap = self._segments[i].end - self._segments[i].start
    +            # The word starts in segment, check if the word ends in it:
    +            elif word.end < self._segments[i].end:
    +                # The overlap is the word's length:
    +                overlap = word.end - word.start
    +            # The word start in segment but ends after it, the overlap is from the word's start to the segment's end:
    +            else:
    +                overlap = self._segments[i].end - word.start
    +            # Check for new best overlap:
    +            if overlap > best_overlap:
    +                best_overlap = overlap
    +                most_overlapping_segment_index = i
    +        if most_overlapping_segment_index is not None:
    +            self._last_chosen_index = most_overlapping_segment_index
    +            return
    +
    +        # If there is no overlapping segment, return the closest segment:
    +        best_distance = None
    +        closest_segment_index = None
    +        for i in possible_segments:
    +            distance = (
    +                word.start - self._segments[i].end
    +                if word.start > self._segments[i].end
    +                else self._segments[i].start - word.end
    +            )
    +            if best_distance is None or distance < best_distance:
    +                best_distance = distance
    +                closest_segment_index = i
    +        self._last_chosen_index = closest_segment_index
    +
    +
    +class SpeechDiarizationPerChannelTask(BaseTask):
    +    """
    +    A task to write the transcription to file with respect to a given speech diarization per channel.
    +    """
    +
    +    class _WordTimestamp(NamedTuple):
    +        """
    +        A word with its start and end timestamps and speaker label (channel the word was taken from).
    +        """
    +
    +        start: float
    +        end: float
    +        speaker: str
    +        text: str
    +
    +    def __init__(self, audio_file: Path, text_file: Path):
    +        """
    +        Initialize the task.
    +
    +        :param audio_file: Path to the audio file that was transcribed.
    +        :param text_file:  Path to the text file to write the transcription to.
    +        """
    +        super().__init__(
    +            audio_file=audio_file, transcription_output={}, text_file=text_file
    +        )
    +        self._transcription_output_channels: List[Tuple[str, dict]] = []
    +
    +    @property
    +    def transcription_output_channels(self) -> List[Tuple[str, dict]]:
    +        """
    +        Get the transcription output channels.
    +
    +        :returns: The transcription output channels.
    +        """
    +        return self._transcription_output_channels
    +
    +    def do_task(self):
    +        """
    +        Try to perform the task storing an error if occurred.
    +        """
    +        for _, channel_output in self._transcription_output_channels:
    +            if isinstance(channel_output, str):
    +                self._error = self._transcription_output_channels
    +                return
    +        super().do_task()
    +
    +    def to_tuple(self) -> Tuple[str, dict]:
    +        """
    +        Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue).
    +
    +        :returns: The converted task.
    +        """
    +        task_class, task_kwargs = super().to_tuple()
    +        task_kwargs.pop("transcription_output")
    +        return task_class, task_kwargs
    +
    +    def _do_task(self):
    +        """
    +        Perform the task - write the transcription to the stored file path with respect to the given speech diarization
    +        per channel.
    +        """
    +        # Cast the chunks to word timestamps tuples:
    +        words_per_channel = [
    +            [
    +                SpeechDiarizationPerChannelTask._WordTimestamp(
    +                    start=chunk["timestamp"][0],
    +                    end=chunk["timestamp"][1],
    +                    speaker=speaker,
    +                    text=chunk["text"],
    +                )
    +                for chunk in output["chunks"]
    +            ]
    +            for speaker, output in self._transcription_output_channels
    +        ]
    +
    +        # Merge and sort the words per channel by their start time:
    +        words = operator.add(*words_per_channel)
    +        words.sort()
    +
    +        # Write the transcription to file:
    +        current_speaker = words[0].speaker
    +        text = f"{current_speaker}:"
    +        for word in words:
    +            # Check if the word's speaker is different from the current one:
    +            if word.speaker != current_speaker:
    +                # Append a newline and update the new speaker:
    +                current_speaker = word.speaker
    +                text += f"\n{current_speaker}:"
    +            # Collect the word:
    +            text += word.text
    +
    +        # Update the transcription output with the new text to write it to file:
    +        self._transcription_output["text"] = text
    +        super()._do_task()
    +
    +
    +class BatchProcessor:
    +    """
    +    A batch processor to process batches of transcriptions. The batch processor is creating tasks and is aimed to be
    +    working along the transcriber. It can be used with multiprocessing queue or run the tasks directly using the
    +    associated methods.
    +    """
    +
    +    def __init__(self, audio_files: List[Path], output_directory: Path):
    +        """
    +        Initialize the batch processor.
    +
    +        :param audio_files:      The list of all audio files to transcribe.
    +        :param output_directory: The output directory to write the transcriptions to.
    +        """
    +        # Store the parameters:
    +        self._audio_files = audio_files
    +        self._output_directory = output_directory
    +
    +        # Prepare the batching variables:
    +        self._current_file_index = 0
    +        self._tasks: List[BaseTask] = []
    +        self._results: List[Tuple[bool, Tuple[str, str]]] = []
    +
    +    def process_batch(self, batch: List[Union[dict, str]]):
    +        """
    +        Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch
    +        processor.
    +
    +        :param batch: The batch of transcriptions to process.
    +        """
    +        # Get the relevant files belongs to the given batch:
    +        current_files = self._get_current_files(batch_size=len(batch))
    +
    +        # Build the diarization tasks:
    +        self._tasks.extend(
    +            [
    +                BaseTask(
    +                    audio_file=file,
    +                    transcription_output=batch[i],
    +                    text_file=self._output_directory / f"{file.stem}.txt",
    +                )
    +                for i, file in enumerate(current_files)
    +            ]
    +        )
    +
    +    def get_tasks(self) -> List[BaseTask]:
    +        """
    +        Get the tasks to perform.
    +
    +        :returns: The tasks to perform.
    +        """
    +        tasks = self._tasks
    +        self._tasks = []
    +        return tasks
    +
    +    def do_tasks(self):
    +        """
    +        Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber.
    +        """
    +        for task in self.get_tasks():
    +            task.do_task()
    +            self._results.append((task.is_failed(), task.get_result()))
    +
    +    def get_results(self) -> List[Tuple[bool, Tuple[str, str]]]:
    +        """
    +        Get the results of the tasks. The stored results are then cleared.
    +
    +        :returns: The results of the tasks.
    +        """
    +        results = self._results
    +        self._results = []
    +        return results
    +
    +    def _get_current_files(self, batch_size: int) -> List[Path]:
    +        """
    +        Get the current files to process.
    +
    +        :param batch_size: The batch size to progress the current file index.
    +
    +        :returns: The current files to process.
    +        """
    +        end_index = (
    +            self._current_file_index + batch_size
    +            if self._current_file_index + batch_size < len(self._audio_files)
    +            else len(self._audio_files)
    +        )
    +        current_files = self._audio_files[self._current_file_index : end_index]
    +        self._current_file_index = end_index
    +        return current_files
    +
    +
    +class SpeechDiarizationBatchProcessor(BatchProcessor):
    +    """
    +    A batch processor to process batches of transcriptions with respect to a given speech diarization. The batch
    +    processor is creating tasks and is aimed to be working along the transcriber. It can be used with multiprocessing
    +    queue or run the tasks directly using the associated methods.
    +    """
    +
    +    def __init__(
    +        self, audio_files: List[Path], output_directory: Path, speech_diarization: dict
    +    ):
    +        """
    +        Initialize the batch processor.
    +
    +        :param audio_files:        The list of all audio files to transcribe.
    +        :param output_directory:   The output directory to write the transcriptions to.
    +        :param speech_diarization: A speech diarization dictionary to pass along with each processed batch.
    +        """
    +        super().__init__(audio_files=audio_files, output_directory=output_directory)
    +        self._speech_diarization = speech_diarization
    +        self._audio_files = audio_files
    +
    +    def process_batch(self, batch: List[dict]):
    +        """
    +        Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch
    +        processor.
    +
    +        :param batch: The batch of transcriptions to process.
    +        """
    +        # Get the relevant files belongs to the given batch:
    +        current_files = self._get_current_files(batch_size=len(batch))
    +
    +        # Build the diarization tasks:
    +        self._tasks.extend(
    +            [
    +                SpeechDiarizationTask(
    +                    audio_file=file,
    +                    transcription_output=batch[i],
    +                    text_file=self._output_directory / f"{file.stem}.txt",
    +                    speech_diarization=self._speech_diarization.get(file.name),
    +                )
    +                for i, file in enumerate(current_files)
    +            ]
    +        )
    +
    +
    +class PerChannelSpeechDiarizationBatchProcessor(BatchProcessor):
    +    """
    +    A batch processor to process batches of transcriptions per channel. The batch processor is creating tasks with the
    +    selected amount of channels given and is aimed to be working along the transcriber. It can be used with
    +    multiprocessing queue or run the tasks directly using the associated methods.
    +    """
    +
    +    def __init__(
    +        self,
    +        audio_files: List[Path],
    +        output_directory: Path,
    +        n_channels: int,
    +        speakers: List[str],
    +    ):
    +        """
    +        Initialize the batch processor.
    +
    +        :param audio_files:      The list of all audio files to transcribe.
    +        :param output_directory: The output directory to write the transcriptions to.
    +        :param n_channels:       The number of channels in each audio file to transcribe.
    +        :param speakers:         The speakers labels to use for each channel.
    +        """
    +        super().__init__(audio_files=audio_files, output_directory=output_directory)
    +
    +        # Store the parameters:
    +        self._n_channels = n_channels
    +        self._speakers = speakers
    +
    +        # Prepare a channel buffer to store the channels until the current task created is fully covered:
    +        self._task_in_process: SpeechDiarizationPerChannelTask = None
    +
    +    def process_batch(self, batch: List[dict]):
    +        """
    +        Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch
    +        processor.
    +
    +        :param batch: The batch of transcriptions to process.
    +        """
    +        # Go over the batch and create the tasks:
    +        for output in batch:
    +            # Check if there is a task in process:
    +            if not self._task_in_process:
    +                # Create a new task:
    +                self._task_in_process = SpeechDiarizationPerChannelTask(
    +                    audio_file=self._audio_files[self._current_file_index],
    +                    text_file=self._output_directory
    +                    / f"{self._audio_files[self._current_file_index].stem}.txt",
    +                )
    +            # Get the channel's speaker:
    +            speaker = self._speakers[
    +                len(self._task_in_process.transcription_output_channels)
    +            ]
    +            # Collect the channel into the processed task:
    +            self._task_in_process.transcription_output_channels.append(
    +                (speaker, output)
    +            )
    +            # Check if the task is fully covered (all channels are collected):
    +            if (
    +                len(self._task_in_process.transcription_output_channels)
    +                == self._n_channels
    +            ):
    +                # Collect the task and reset the task in process:
    +                self._tasks.append(self._task_in_process)
    +                self._current_file_index += 1
    +                self._task_in_process = None
    +
    +
    +class Transcriber:
    +    """
    +    A transcription wrapper for the Huggingface's ASR pipeline -
    +    https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline to
    +    use with OpenAI's Whisper models - https://huggingface.co/openai.
    +    """
    +
    +    def __init__(
    +        self,
    +        model_name: str,
    +        device: str = None,
    +        use_flash_attention_2: bool = None,
    +        use_better_transformers: bool = None,
    +        assistant_model: str = None,
    +        max_new_tokens: int = 128,
    +        chunk_length_s: int = 30,
    +        batch_size: int = 2,
    +        spoken_language: str = None,
    +        translate_to_english: bool = False,
    +        return_timestamps: Union[bool, Literal["word"]] = False,
    +        per_channel_transcription: int = 0,
    +    ):
    +        """
    +        Initialize the transcriber.
    +
    +        :param model_name:                The model name to use. Should be a model from the OpenAI's Whisper models for
    +                                          best results (for example "tiny", "base", "large", etc.).
    +        :param device:                    The device to use for inference. If not given, will use GPU if available.
    +        :param use_flash_attention_2:     Whether to use the Flash Attention 2 implementation. It can be used only with
    +                                          one of the following GPUs: Nvidia H series and Nvidia A series. T4 support
    +                                          will be available soon.
    +
    +                                          Note: If both `use_flash_attention_2` and
    +                                          `use_better_transformers` are `None`, the optimization will be chosen
    +                                          automatically according to the available resources.
    +
    +        :param use_better_transformers:   Whether to use the Better Transformers library to further optimize the model.
    +                                          Should be used for all use cases that do not support flash attention 2.
    +
    +                                          Note: If both `use_flash_attention_2` and `use_better_transformers` are
    +                                          `None`, the optimization will be chosen automatically according to the
    +                                          available resources.
    +       :param assistant_model:           The assistant model name to use for inference. Notice that the optimizations
    +                                          (flash attention 2 and better transformers) will be applied for the assistant
    +                                          as well. Should be a model from Huggingface's distil-whisper (see here for
    +                                          more information: https://github.com/huggingface/distil-whisper).
    +        :param max_new_tokens:            The maximum number of new tokens to generate. This is used to limit the
    +                                          generation length. Default is 128 tokens.
    +        :param chunk_length_s:            The audio chunk to split the audio to (in seconds). Default is 30 seconds.
    +        :param batch_size:                The batch size to use for inference. Default is 2.
    +        :param spoken_language:           Aim whisper to know what language is spoken. If None, it will try to detect it
    +                                          for each chunk.
    +        :param translate_to_english:      Whether to translate the transcriptions to English. Default is False.
    +        :param return_timestamps:         Whether to return the timestamps of the words. If "word", will return the
    +                                          timestamps of each word. If True will return the timestamps of each chunk.
    +                                          Default is False. Aimed to be used for speech diarization.
    +        :param per_channel_transcription: Whether to do per channel transcription. If needed to run per channel
    +                                          transcription, pass the number of channels expected for each audio file here.
    +                                          0 means regular transcription (merge channels).
    +
    +                                          Note: If `per_channel_transcription` is not 0, `batch_size` must be treated to
    +                                          be the number of channels and not audio files. Aimed to be used for per
    +                                          channel speech diarization.
    +        """
    +        # Store loading parameters:
    +        self._model_name = model_name
    +        self._device = device
    +        self._use_flash_attention_2 = use_flash_attention_2
    +        self._use_better_transformers = use_better_transformers
    +        self._max_new_tokens = max_new_tokens
    +        self._chunk_length_s = chunk_length_s
    +        self._batch_size = batch_size
    +        self._return_timestamps = return_timestamps
    +        self._per_channel_transcription = per_channel_transcription
    +
    +        # Store generation parameters:
    +        self._assistant_model = assistant_model
    +        self._spoken_language = spoken_language
    +        self._translate_to_english = translate_to_english
    +
    +        # Prepare the transcription objects:
    +        self._transcription_pipeline: AutomaticSpeechRecognitionPipeline = None
    +        self._generate_kwargs: dict = None
    +
    +    def load(self):
    +        """
    +        Load the transcriber. Must be called before transcribing.
    +        """
    +        # Set the device and data type to use (prefer GPU if available):
    +        device = torch.device(
    +            self._device or "cuda" if torch.cuda.is_available() else "cpu"
    +        )
    +        torch_dtype = torch.float16 if device.type == "cuda" else torch.float32
    +
    +        # Choose the optimization to use (in case the user did not specify any):
    +        if (
    +            self._use_flash_attention_2 is None
    +            and self._use_better_transformers is None
    +        ):
    +            # Prefer to use flash attention 2 if available and cuda device is supported (see GPU names to architecture
    +            # here: https://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units#Tesla):
    +            if device.type == "cuda" and is_flash_attn_2_available():
    +                cuda_device_name = torch.cuda.get_device_properties(device).name
    +                if any(
    +                    cuda_device_name.startswith(gpu_name)
    +                    for gpu_name in [
    +                        "NVIDIA A",  # For Ampere architecture (e.g. A10, A30, A100)
    +                        "NVIDIA H",  # For Hopper architecture (e.g. H100)
    +                        "NVIDIA L",  # For Ada Lovelace architecture (e.g. L4, L40)
    +                        "NVIDIA RTX 30",  # For Ada Lovelace architecture (RTX 30 series)
    +                        "NVIDIA RTX 40",  # For Ada Lovelace architecture (RTX 40 series)
    +                        "NVIDIA RTX 50",  # For Ada Lovelace architecture (RTX 50 series)
    +                        # Will be supported soon according to FlashAttention GitHub repo:
    +                        # https://github.com/Dao-AILab/flash-attention?tab=readme-ov-file#installation-and-features
    +                        # "NVIDIA T4",  # For Turing architecture (only T4)
    +                        # "NVIDIA RTX 20",  # For Turing architecture (RTX 20 series)
    +                    ]
    +                ):
    +                    self._use_flash_attention_2 = True
    +                else:
    +                    self._use_better_transformers = True
    +            else:
    +                self._use_better_transformers = True
    +
    +        # Build the optimizations kwargs:
    +        model_kwargs = {
    +            "low_cpu_mem_usage": True,
    +            "use_safetensors": True,
    +        }
    +        if self._use_flash_attention_2:
    +            if _LOGGER:
    +                _LOGGER.info(
    +                    "Using FlashAttention2 optimization - make sure the `flash-attn` package is installed via "
    +                    "`pip install -U flash-attn --no-build-isolation`"
    +                )
    +            model_kwargs["attn_implementation"] = "flash_attention_2"
    +        elif self._use_better_transformers:
    +            if _LOGGER:
    +                _LOGGER.info(
    +                    "Using BetterTransformers optimization - make sure the `optimum` package is installed via "
    +                    "`pip install -U optimum`"
    +                )
    +            model_kwargs["attn_implementation"] = "sdpa"
    +
    +        # Initialize the speech recognition pipeline:
    +        self._transcription_pipeline = pipeline(
    +            task="automatic-speech-recognition",
    +            model=self._model_name,
    +            model_kwargs=model_kwargs.copy(),
    +            batch_size=self._batch_size,
    +            max_new_tokens=self._max_new_tokens,
    +            chunk_length_s=self._chunk_length_s,
    +            return_timestamps=self._return_timestamps,
    +            torch_dtype=torch_dtype,
    +            device=device,
    +        )
    +
    +        # Prepare the generation kwargs:
    +        self._generate_kwargs = {
    +            "language": self._spoken_language,
    +            "task": "translate" if self._translate_to_english else "transcribe",
    +        }
    +
    +        # Initialize the assistant model (if needed):
    +        if self._assistant_model:
    +            assistant_model = AutoModelForCausalLM.from_pretrained(
    +                self._assistant_model, torch_dtype=torch_dtype, **model_kwargs
    +            )
    +            assistant_model.to(device)
    +            self._generate_kwargs["assistant_model"] = assistant_model
    +
    +    def transcribe(
    +        self,
    +        audio_files: List[Path],
    +        batch_processor: BatchProcessor = None,
    +        batches_queue: Queue = None,
    +        verbose: bool = False,
    +    ) -> Union[List[List[dict]], None]:
    +        """
    +        Transcribe the given audio files. The transcriptions will be sent to a queue or a batch processor for further
    +        processing like writing to text files. If no queue or batch processor is given, the transcriptions outputs from
    +        the pipeline will be returned. Otherwise, `None` is returned.
    +
    +        :param audio_files:     The audio files to transcribe.
    +        :param batch_processor: A batch processor.
    +        :param batches_queue:   A multiprocessing queue to put the batches in.
    +        :param verbose:         Whether to show a progress bar. Default is False.
    +
    +        :returns: The transcriptions outputs from the pipeline if no queue or batch processor is given, otherwise,
    +                  `None`.
    +        """
    +        # Wrap the audio files with a function to iterate over them via a generator (save memory and runtime with
    +        # Huggingface's pipelines as they preload each input while inference is running):
    +        def audio_iterator() -> Generator[Union[dict, str], None, None]:
    +            if self._per_channel_transcription:
    +                for audio_file in audio_files:
    +                    audio, sampling_rate = torchaudio.load(str(audio_file))
    +                    audio = audio.numpy()
    +                    for channel in audio:
    +                        yield {"raw": channel, "sampling_rate": sampling_rate}
    +            else:
    +                for audio_file in audio_files:
    +                    yield str(audio_file)
    +
    +        # Create a batch iterator:
    +        def batch_iterator() -> Generator[List[Union[dict, str]], None, None]:
    +            batch = []
    +            for audio in audio_iterator():
    +                batch.append(audio)
    +                if len(batch) == self._batch_size:
    +                    yield batch
    +                    batch = []
    +            if batch:
    +                yield batch
    +
    +        # Prepare the successes dataframe and errors dictionary to be returned:
    +        outputs = []
    +
    +        # Infer through the pipeline:
    +        for input_batch in tqdm(
    +            batch_iterator() if self._batch_size > 1 else audio_iterator(),
    +            desc="Transcribing",
    +            unit="channel" if self._per_channel_transcription else "audio file",
    +            total=(
    +                (
    +                    (len(audio_files) // self._batch_size)
    +                    + (len(audio_files) % self._batch_size != 0)
    +                )
    +                * (self._per_channel_transcription or 1)
    +            ),
    +            disable=not verbose,
    +        ):
    +            # Infer:
    +            try:
    +                output_batch = self._transcription_pipeline(
    +                    input_batch,
    +                    generate_kwargs=self._generate_kwargs,
    +                )
    +            except Exception as exception:
    +                # Collect the exception:
    +                output_batch = str(exception)
    +                # Align to batch size:
    +                output_batch = (
    +                    [output_batch] * len(input_batch)
    +                    if isinstance(input_batch, list)
    +                    else [output_batch]
    +                )
    +            # To align with batching, if batch size is 1, wrap the output with a list:
    +            if isinstance(output_batch, dict):
    +                output_batch = [output_batch]
    +            # If a batch processor is given, process the batch:
    +            if batch_processor:
    +                # Process it directly:
    +                batch_processor.process_batch(batch=output_batch)
    +                batch_processor.do_tasks()
    +            elif batches_queue:
    +                # Otherwise, queue the batch:
    +                batches_queue.put(output_batch)
    +            else:
    +                # Otherwise, collect the output as is without processing:
    +                outputs.append(output_batch)
    +
    +        # Check if given a multiprocessing queue or a batch processor:
    +        if batches_queue:
    +            batches_queue.put(_MULTIPROCESSING_STOP_MARK)
    +
    +        return outputs if not batch_processor else None
    +
    +
    +#: The value to send into multiprocessing queues to stop the process:
    +_MULTIPROCESSING_STOP_MARK = "STOP"
    +
    +
    +def _multiprocessing_process_batches(
    +    batch_processor: BatchProcessor,
    +    batches_queue: Queue,
    +    tasks_queue: Queue,
    +    n_task_completers: int,
    +):
    +    """
    +    Process the batches in the given batches queue and put the tasks in the given tasks queue. The function will stop
    +    when the given batches queue will receive the stop mark. It is aimed to be used with multiprocessing as a process.
    +
    +    :param batch_processor:   A batch processor to process the batches.
    +    :param batches_queue:     A queue to get the batches from.
    +    :param tasks_queue:       A queue to put the tasks in.
    +    :param n_task_completers: The number of task completers (processes that run the `_multiprocessing_complete_tasks`
    +                              function). A stop mark will be sent to the tasks queue for each task completer.
    +    """
    +    while True:
    +        # Get the batch:
    +        batch: List[dict] = batches_queue.get()
    +        if batch == _MULTIPROCESSING_STOP_MARK:
    +            break
    +
    +        # Process the batch:
    +        batch_processor.process_batch(batch=batch)
    +
    +        # Get the tasks:
    +        tasks = batch_processor.get_tasks()
    +
    +        # Queue the tasks:
    +        for task in tasks:
    +            tasks_queue.put(task.to_tuple())
    +
    +    # Mark the end of the batches:
    +    for _ in range(n_task_completers):
    +        tasks_queue.put(_MULTIPROCESSING_STOP_MARK)
    +
    +
    +def _multiprocessing_complete_tasks(tasks_queue: Queue, results_queue: Queue):
    +    """
    +    Complete the tasks in the given queue and put the results in the given results queue. The function will stop when
    +    the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process.
    +
    +    :param tasks_queue:   A queue to get the tasks from.
    +    :param results_queue: A queue to put the results in.
    +    """
    +    tasks_map = {
    +        BaseTask.__name__: BaseTask,
    +        SpeechDiarizationTask.__name__: SpeechDiarizationTask,
    +        SpeechDiarizationPerChannelTask.__name__: SpeechDiarizationPerChannelTask,
    +    }
    +
    +    while True:
    +        # Get the task:
    +        task = tasks_queue.get()
    +        if task == _MULTIPROCESSING_STOP_MARK:
    +            break
    +
    +        # Reconstruct the task:
    +        task_class, task_kwargs = task
    +        task = tasks_map[task_class](**task_kwargs)
    +
    +        # Complete the task:
    +        task.do_task()
    +        results_queue.put((task.is_failed(), task.get_result()))
    +
    +    # Mark the end of the tasks:
    +    results_queue.put(_MULTIPROCESSING_STOP_MARK)
    +
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def open_mpi_handler(
    +    worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None
    +):
    +    global _LOGGER
    +
    +    # Check for MLRun and OpenMPI availability:
    +    context, comm = _check_mlrun_and_open_mpi()
    +
    +    # Check if MLRun is available, set the global logger to MLRun's:
    +    if context:
    +        _LOGGER = context.logger
    +
    +    def decorator(handler):
    +        if comm is None or comm.Get_size() == 1:
    +            return handler
    +
    +        @wraps(handler)
    +        def wrapper(**kwargs):
    +            # Get the open mpi environment properties:
    +            size = comm.Get_size()
    +            rank = comm.Get_rank()
    +
    +            # Give the correct chunk of the workers inputs:
    +            for worker_input in worker_inputs:
    +                input_argument = kwargs[worker_input]
    +                if input_argument is None:
    +                    continue
    +                if isinstance(input_argument, str):
    +                    input_argument = _get_audio_files(
    +                        data_path=Path(input_argument).absolute()
    +                    )
    +                if len(input_argument) < size:
    +                    raise ValueError(
    +                        f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. "
    +                        f"Please reduce the amount of workers for this input."
    +                    )
    +                even_chunk_size = len(input_argument) // size
    +                chunk_start = rank * even_chunk_size
    +                chunk_end = (
    +                    (rank + 1) * even_chunk_size
    +                    if rank + 1 < size
    +                    else len(input_argument)
    +                )
    +                context.logger.info(
    +                    f"Rank #{rank}: Processing input chunk of '{worker_input}' "
    +                    f"from index {chunk_start} to {chunk_end}."
    +                )
    +                if isinstance(input_argument, list):
    +                    input_argument = input_argument[chunk_start:chunk_end]
    +                elif isinstance(input_argument, pd.DataFrame):
    +                    input_argument = input_argument.iloc[chunk_start:chunk_end:, :]
    +                kwargs[worker_input] = input_argument
    +
    +            # Set the root worker only arguments:
    +            if rank == 0 and root_worker_inputs:
    +                kwargs.update(root_worker_inputs)
    +
    +            # Run the worker:
    +            output = handler(**kwargs)
    +
    +            # Save the output directory of this worker:
    +            output_directory = Path(output[0])
    +
    +            # Send the output to the root rank (rank #0):
    +            output = comm.gather(output, root=0)
    +
    +            # Join the data from all workers:
    +            if rank == 0:
    +                context.logger.info("Collecting data from workers to root worker.")
    +
    +                # Check if there are different output directories:
    +                output_directories = set([Path(out_dir) for out_dir, _, _ in output])
    +                for r in range(1, size):
    +                    # True means the other workers should pass their files to the root worker (rank 0):
    +                    comm.send(len(output_directories) != 1, dest=r)
    +
    +                # If there are different output directories, listen to the other workers:
    +                if len(output_directories) != 1:
    +                    # Collect the files from the other workers:
    +                    files = []
    +                    for r in range(1, size):
    +                        files.extend(comm.recv(source=r))
    +                    # Write the files to the root worker's output directory:
    +                    for file_name, file_content in files:
    +                        with open(output_directory / file_name, "w") as f:
    +                            f.write(file_content)
    +
    +                # Concatenate the dataframes:
    +                dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0)
    +
    +                # Concatenate the errors dictionaries:
    +                errors_dictionary = reduce(
    +                    operator.ior, [err for _, _, err in output], {}
    +                )
    +
    +                return str(output_directory), dataframe, errors_dictionary
    +
    +            # Listen to rank 0 to see if there are different output directories and this rank need to send its files to
    +            # it:
    +            if comm.recv(source=0):
    +                files = []
    +                for file in os.listdir(output_directory):
    +                    with open(output_directory / file, "r") as f:
    +                        files.append((file, f.read()))
    +                comm.send(files, dest=0)
    +            return None
    +
    +        return wrapper
    +
    +    return decorator
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +        else:
    +            return context, None
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True})
    +def transcribe(
    +    # Input / Output kwargs:
    +    data_path: Union[str, Path, List[Union[str, Path]]],
    +    output_directory: str = None,
    +    # Model loading kwargs:
    +    model_name: str = "openai/whisper-tiny",
    +    device: str = None,
    +    use_flash_attention_2: bool = None,
    +    use_better_transformers: bool = None,
    +    # Generation kwargs:
    +    assistant_model: str = None,
    +    max_new_tokens: int = 128,
    +    chunk_length_s: int = 30,
    +    batch_size: int = 8,
    +    spoken_language: str = None,
    +    translate_to_english: bool = False,
    +    # Diarization kwargs:
    +    speech_diarization: Dict[str, List[Tuple[float, float, str]]] = None,
    +    speech_diarize_per_channel: int = None,
    +    speaker_labels: List[str] = None,
    +    # Other kwargs:
    +    use_multiprocessing: Union[bool, int] = False,
    +    verbose: bool = False,
    +):
    +    """
    +    Transcribe audio files into text files and collect additional data. The end result is a directory of transcribed
    +    text files and a dataframe containing the following columns:
    +
    +    * audio_file - The audio file path.
    +    * transcription_file - The transcribed text file name in the output directory.
    +
    +    The transcription is based on Huggingface's ASR pipeline -
    +    https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline and
    +    is tested with OpenAI's Whisper models - https://huggingface.co/openai.
    +
    +    If one of the speaker diarization parameters are given (either `speech_diarization` or
    +    `speech_diarize_per_channel`), the transcription will be written in a conversation format, where each speaker will
    +    be written in a separate line::
    +
    +        speaker_1: text
    +        speaker_2: text
    +        speaker_1: text
    +        ...
    +
    +    :param data_path:                  A directory of audio files or a single file or a list of files to transcribe.
    +    :param output_directory:           Path to a directory to save all transcribed audio files. If not given, will save
    +                                       the transcribed files in a temporary directory.
    +    :param model_name:                 The model name to use. Should be a model from the OpenAI's Whisper models for
    +                                       best results (for example "tiny", "base", "large", etc.). See here for more
    +                                       information: https://huggingface.co/openai?search_models=whisper.
    +    :param device:                     The device to use for inference. If not given, will use GPU if available.
    +    :param use_flash_attention_2:      Whether to use the Flash Attention 2 implementation. It can be used only with
    +                                       one of the following GPUs: Nvidia H series and Nvidia A series. T4 support
    +                                       will be available soon.
    +
    +                                       Note: If both `use_flash_attention_2` and
    +                                       `use_better_transformers` are `None`, the optimization will be chosen
    +                                       automatically according to the available resources.
    +
    +    :param use_better_transformers:    Whether to use the Better Transformers library to further optimize the model.
    +                                       Should be used for all use cases that do not support flash attention 2.
    +
    +                                       Note: If both `use_flash_attention_2` and `use_better_transformers` are
    +                                       `None`, the optimization will be chosen automatically according to the
    +                                       available resources.
    +    :param assistant_model:            The assistant model name to use for inference. Notice that the optimizations
    +                                       (flash attention 2 and better transformers) will be applied for the assistant as
    +                                       well. Should be a model from Huggingface's distil-whisper (see here for more
    +                                       information: https://github.com/huggingface/distil-whisper).
    +
    +                                       Note: Currently an assistant model is only usable with batch size of 1.
    +    :param max_new_tokens:             The maximum number of new tokens to generate. This is used to limit the
    +                                       generation length. Default is 128 tokens.
    +    :param chunk_length_s:             The audio chunk to split the audio to (in seconds). Default is 30 seconds.
    +    :param batch_size:                 The batch size to use for inference. Default is 2.
    +    :param spoken_language:            Aim whisper to know what language is spoken. If None, it will try to detect
    +                                       it.
    +    :param translate_to_english:       Whether to translate the transcriptions to English.
    +    :param speech_diarization:         A speech diarization dictionary with the file names to transcribe as keys and
    +                                       their diarization as value. The diarization is a list of tuples:
    +                                       (start, end, speaker). An example
    +                                       for a diarization dictionary::
    +
    +                                       {
    +                                           "audio_file_name": [
    +                                               {
    +                                                   "start": 0.0,
    +                                                   "end": 2.0,
    +                                                   "speaker": "Agent",
    +                                               },
    +                                               {
    +                                                   "start": 2.0,
    +                                                   "end": 4.0,
    +                                                   "speaker": "Client",
    +                                               },
    +                                               ...
    +                                           ],
    +                                           ...
    +                                       }
    +
    +                                       Note: The diarization must be for the entire duration of the audio file (as long
    +                                       as Whisper is predicting words up until then.
    +    :param speech_diarize_per_channel: Perform speech diarization per channel. Each speaker is expected to belong to
    +                                       a separate channel in the audio. Notice: This will make the transcription
    +                                       slower as each channel wil be transcribed separatly. If a speech diarization
    +                                       is passed (via the `speech_diarization` parameter), this parameter is
    +                                       ignored.
    +    :param speaker_labels:             A list of speaker labels by channel order to use for writing the
    +                                       transcription with respect to per channel speech diarization. This won't be
    +                                       used together with a given speech diarization (via the `speech_diarization`
    +                                       parameter).
    +    :param use_multiprocessing:        Whether to use multiprocessing to transcribe the audio files. Can be either a
    +                                       boolean value or an integer. If `True`, will use the default amount of workers
    +                                       (3): 1 for transcription, 1 for batch processing and 1 for task completion (such
    +                                       as speech diarization and writing to files). To control the amount of tasks
    +                                       completion workers, an integer can be provided to specify the amount of workers.
    +                                       `False`, will use a single process. Default is `False`.
    +    :param verbose:                    Whether to print the progress of the transcription. Default is `False`.
    +    """
    +    global _LOGGER
    +
    +    # Get the input audio files to transcribe:
    +    if verbose:
    +        _LOGGER.info("Collecting audio files.")
    +    audio_files = _get_audio_files(data_path=data_path)
    +    if verbose:
    +        _LOGGER.info(f"Collected {len(audio_files)} audio files.")
    +
    +    # Get the output directory:
    +    if output_directory is None:
    +        if verbose:
    +            _LOGGER.info("No output directory given, using temporary directory.")
    +        output_directory = tempfile.mkdtemp()
    +    output_directory = Path(output_directory).absolute()
    +    output_directory.mkdir(exist_ok=True, parents=True)
    +    if verbose:
    +        _LOGGER.info(f"Transcriptions will be saved to: {output_directory}")
    +
    +    # Initialize a batch processor according to user requirements (no speech diarization, given speech diarization,
    +    # speech diarization per channel):
    +    if speech_diarization:
    +        batch_processor = SpeechDiarizationBatchProcessor(
    +            audio_files=audio_files,
    +            output_directory=output_directory,
    +            speech_diarization=speech_diarization,
    +        )
    +    elif speech_diarize_per_channel:
    +        batch_processor = PerChannelSpeechDiarizationBatchProcessor(
    +            audio_files=audio_files,
    +            output_directory=output_directory,
    +            n_channels=speech_diarize_per_channel,
    +            speakers=speaker_labels,
    +        )
    +    else:
    +        batch_processor = BatchProcessor(
    +            audio_files=audio_files,
    +            output_directory=output_directory,
    +        )
    +
    +    # Initialize the transcription pipeline:
    +    transcriber = Transcriber(
    +        device=device,
    +        use_flash_attention_2=use_flash_attention_2,
    +        use_better_transformers=use_better_transformers,
    +        assistant_model=assistant_model,
    +        model_name=model_name,
    +        max_new_tokens=max_new_tokens,
    +        chunk_length_s=chunk_length_s,
    +        batch_size=batch_size,
    +        return_timestamps=(
    +            "word"
    +            if speech_diarization is not None or speech_diarize_per_channel is not None
    +            else False
    +        ),
    +        per_channel_transcription=speech_diarize_per_channel or 0,
    +        spoken_language=spoken_language,
    +        translate_to_english=translate_to_english,
    +    )
    +
    +    # Run the transcription:
    +    if use_multiprocessing:
    +        results = _parallel_run(
    +            n_workers=use_multiprocessing
    +            if isinstance(use_multiprocessing, int)
    +            else 1,
    +            audio_files=audio_files,
    +            batch_processor=batch_processor,
    +            transcriber=transcriber,
    +            verbose=verbose,
    +        )
    +    else:
    +        results = _run(
    +            audio_files=audio_files,
    +            batch_processor=batch_processor,
    +            transcriber=transcriber,
    +            verbose=verbose,
    +        )
    +
    +    # Process the results:
    +    if verbose:
    +        _LOGGER.info("Summarizing the results.")
    +    successes = []
    +    errors = {}
    +    for is_error, result in results:
    +        if is_error:
    +            errors[result[0]] = result[1]
    +        else:
    +            successes.append(result)
    +    successes = pd.DataFrame(successes, columns=["audio_file", "transcription_file"])
    +    if verbose:
    +        _LOGGER.info(
    +            f"Done ({successes.shape[0]}/{len(audio_files)})\n"
    +            f"Transcriptions summary:\n"
    +            f"{successes.head()}"
    +        )
    +
    +    return str(output_directory), successes, errors
    +
    +
    +def _get_audio_files(
    +    data_path: Union[Path, str, list],
    +) -> List[Path]:
    +    """
    +    Get the audio files to transcribe. If a path to a directory is given, all files in the directory will be collected.
    +
    +    :param data_path: The data path to collect the audio files from.
    +
    +    :returns: The audio files list.
    +    """
    +    # Check if given a list of paths:
    +    if isinstance(data_path, list):
    +        audio_files = []
    +        for path in data_path:
    +            audio_files.extend(_get_audio_files(data_path=path))
    +        return audio_files
    +
    +    # Check if given a single string path to cast it to a `pathlib.Path`:
    +    if isinstance(data_path, str):
    +        data_path = Path(data_path).absolute()
    +
    +    # Check if the path is of a directory or a file:
    +    if data_path.is_dir():
    +        # Get all files inside the directory:
    +        audio_files = list(data_path.glob("*.*"))
    +    elif data_path.is_file():
    +        audio_files = [data_path]
    +    else:
    +        raise ValueError(
    +            f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a "
    +            f"file. Given: {str(data_path)} "
    +        )
    +
    +    return audio_files
    +
    +
    +def _run(
    +    audio_files: List[Path],
    +    batch_processor: BatchProcessor,
    +    transcriber: Transcriber,
    +    verbose: bool,
    +) -> List[Tuple[bool, Tuple[str, str]]]:
    +    """
    +    Run the transcription without multiprocessing.
    +
    +    :param audio_files:     The audio files to transcribe.
    +    :param batch_processor: The batch processor to use.
    +    :param transcriber:     The transcriber to use.
    +    :param verbose:         Verbosity.
    +
    +    :returns: The collected results.
    +    """
    +    # Load the transcription pipeline:
    +    if verbose:
    +        _LOGGER.info(f"Loading the transcription pipeline.")
    +    transcriber.load()
    +    if verbose:
    +        _LOGGER.info("Transcription pipeline loaded.")
    +
    +    # Transcribe the files:
    +    transcriber.transcribe(
    +        audio_files=audio_files,
    +        batch_processor=batch_processor,
    +        verbose=verbose,
    +    )
    +
    +    # Return the results:
    +    return batch_processor.get_results()
    +
    +
    +def _parallel_run(
    +    n_workers: int,
    +    audio_files: List[Path],
    +    batch_processor: BatchProcessor,
    +    transcriber: Transcriber,
    +    verbose: bool,
    +):
    +    """
    +    Run the transcription with multiprocessing.
    +
    +    :param n_workers:       The amount of workers to use as task completers.
    +    :param audio_files:     The audio files to transcribe.
    +    :param batch_processor: The batch processor to use.
    +    :param transcriber:     The transcriber to use.
    +    :param verbose:         Verbosity.
    +
    +    :returns: The collected results.
    +    """
    +    # Initialize the multiprocessing queues:
    +    batches_queue = Queue()
    +    tasks_queue = Queue()
    +    results_queue = Queue()
    +
    +    # Initialize the multiprocessing processes:
    +    batch_processing_process = Process(
    +        target=_multiprocessing_process_batches,
    +        kwargs={
    +            "batch_processor": batch_processor,
    +            "batches_queue": batches_queue,
    +            "tasks_queue": tasks_queue,
    +            "n_task_completers": n_workers,
    +        },
    +    )
    +    task_completion_processes = [
    +        Process(
    +            target=_multiprocessing_complete_tasks,
    +            kwargs={"tasks_queue": tasks_queue, "results_queue": results_queue},
    +        )
    +        for _ in range(n_workers)
    +    ]
    +
    +    # Start the multiprocessing processes:
    +    batch_processing_process.start()
    +    for p in task_completion_processes:
    +        p.start()
    +
    +    # Load the transcription pipeline:
    +    if verbose:
    +        _LOGGER.info(f"Loading the transcription pipeline.")
    +    transcriber.load()
    +    if verbose:
    +        _LOGGER.info("Transcription pipeline loaded.")
    +
    +    # Transcribe the files:
    +    transcriber.transcribe(
    +        audio_files=audio_files, batches_queue=batches_queue, verbose=verbose
    +    )
    +
    +    # Collect the results:
    +    results = []
    +    stop_marks_counter = 0
    +    while True:
    +        # Get a result from the queue:
    +        result: Tuple[bool, Tuple[str, str]] = results_queue.get()
    +        if result == _MULTIPROCESSING_STOP_MARK:
    +            stop_marks_counter += 1
    +            if stop_marks_counter == n_workers:
    +                break
    +        else:
    +            # Collect the result:
    +            results.append(result)
    +
    +    # Wait for the processes to finish:
    +    results_queue.empty()
    +    batch_processing_process.join()
    +    for p in task_completion_processes:
    +        p.join()
    +
    +    return results
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/transcribe/1.2.0/static/transcribe.html b/functions/master/transcribe/1.2.0/static/transcribe.html new file mode 100644 index 00000000..7f277c56 --- /dev/null +++ b/functions/master/transcribe/1.2.0/static/transcribe.html @@ -0,0 +1,1708 @@ + + + + + + + +transcribe.transcribe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for transcribe.transcribe

    +# Copyright 2024 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +import logging
    +import operator
    +import os
    +import tempfile
    +from functools import reduce, wraps
    +from multiprocessing import Process, Queue
    +from pathlib import Path
    +from typing import Any, Dict, Generator, List, Literal, NamedTuple, Tuple, Union
    +
    +import pandas as pd
    +import torch
    +import torchaudio
    +from tqdm import tqdm
    +from transformers import (
    +    AutomaticSpeechRecognitionPipeline,
    +    AutoModelForCausalLM,
    +    pipeline,
    +)
    +from transformers.utils import is_flash_attn_2_available
    +
    +
    +
    +[docs] +class BaseTask: + """ + A task to write the transcription to file. + """ + + def __init__( + self, audio_file: Path, transcription_output: Union[dict, str], text_file: Path + ): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param transcription_output: The transcription output from the pipeline. String means an exception was raised. + :param text_file: Path to the text file to write the transcription to. + """ + # Store the parameters: + self._audio_file = audio_file + self._transcription_output = transcription_output + self._text_file = text_file + + # Prepare the error variable: + self._error: str = None + +
    +[docs] + def do_task(self): + """ + Try to perform the task storing an error if occurred. + """ + if isinstance(self._transcription_output, str): + self._error = self._transcription_output + return + try: + self._do_task() + except Exception as exception: + self._error = str(exception)
    + + +
    +[docs] + def is_failed(self) -> bool: + """ + Check if the task failed. + + :returns: Whether the task failed. + """ + return self._error is not None
    + + +
    +[docs] + def get_result(self) -> Tuple[str, str]: + """ + Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the + text file name. + + :returns: The task's result. + """ + if self.is_failed(): + return self._audio_file.name, self._error + return self._audio_file.name, self._text_file.name
    + + +
    +[docs] + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + return self.__class__.__name__, { + "audio_file": self._audio_file, + "transcription_output": self._transcription_output, + "text_file": self._text_file, + }
    + + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path. + """ + # Checking for no duplications: + i = 1 + while self._text_file.exists(): + i += 1 + self._text_file = ( + self._text_file.parent + / f"{self._text_file.stem.rsplit('_', 1)[0]}_{i}{self._text_file.suffix}" + ) + + # Make sure all directories are created: + self._text_file.parent.mkdir(exist_ok=True, parents=True) + + # Write to file: + with open(self._text_file, "w") as fp: + fp.write(self._transcription_output["text"])
    + + + +
    +[docs] +class SpeechDiarizationTask(BaseTask): + """ + A task to write the transcription to file with respect to a given speech diarization. + """ + + class _DiarizationSegment(NamedTuple): + """ + A speech diarization segment. + """ + + start: float + end: float + speaker: str + + class _WordTimestamp(NamedTuple): + """ + A word with its start and end timestamps. + """ + + start: float + end: float + text: str + + def __init__( + self, + audio_file: Path, + transcription_output: dict, + text_file: Path, + speech_diarization: List[Tuple[float, float, str]], + ): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param transcription_output: The transcription output from the pipeline. + :param text_file: Path to the text file to write the transcription to. + :param speech_diarization: A speech diarization as a list of tuples: (start, end, speaker). + """ + super().__init__( + audio_file=audio_file, + transcription_output=transcription_output, + text_file=text_file, + ) + self._speech_diarization = speech_diarization + self._segments: List[SpeechDiarizationTask._DiarizationSegment] = None + self._last_chosen_index = 0 + +
    +[docs] + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + return task_class, { + **task_kwargs, + "speech_diarization": self._speech_diarization, + }
    + + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path with respect to the given speech diarization. + """ + # Check if a speech diarization is given, if not, just write the transcription to file: + if not self._speech_diarization: + super()._do_task() + return + + # Cast the chunks to word timestamps tuples: + words = [ + SpeechDiarizationTask._WordTimestamp( + start=chunk["timestamp"][0], + end=chunk["timestamp"][1], + text=chunk["text"], + ) + for chunk in self._transcription_output["chunks"] + ] + + # Cast speech diarization to segments tuples: + self._segments = [ + SpeechDiarizationTask._DiarizationSegment(*segment) + for segment in self._speech_diarization + ] + + # Try to match the Whisper model predicted timestamps to the closest diarization segment (closest diarization + # segment will be the most overlapping with the word, and if there is no overlap, the closest segment to the + # word): + speaker = self._segments[self._last_chosen_index].speaker + text = f"{speaker}:" + for word in words: + # Get the next diarization segment: + self._get_next_segment(word=word) + # Check if the segment is of the same speaker: + if self._segments[self._last_chosen_index].speaker == speaker: + # Collect the word: + text += word.text + else: + # Append a newline and update the new speaker: + speaker = self._segments[self._last_chosen_index].speaker + text += f"\n{speaker}:{word.text}" + + # Update the transcription output with the new text to write it to file: + self._transcription_output["text"] = text + super()._do_task() + + def _get_next_segment( + self, + word: _WordTimestamp, + ): + """ + Get the next diarization segment the given word falls into. The `self._last_chosen_index` will be updated + accordingly. + + :param word: The word timestamp to match to the next segment. + """ + # If the last chosen segment is the last segment, return it: + if self._last_chosen_index == len(self._segments) - 1: + return + + # Get the last chosen diarization segment: + last_chosen = self._segments[self._last_chosen_index] + + # None value may appear if the word is the last word in the audio file, or it was split during inference. In + # that case, we'll set the last segment: + if word.end is None: + self._last_chosen_index = len(self._segments) - 1 + return + + # If the word ends before the last chosen segment: + if word.end <= last_chosen.start: + # Then it is still the closest segment + return + + # We check if it ends inside the last chosen segment: + if word.end < last_chosen.end: + # Then it still is the closest segment + return + + # The word ends after the segment, we need to collect all next segments up until the word ends before them: + possible_segments = [self._last_chosen_index] + for i in range(self._last_chosen_index + 1, len(self._segments)): + if word.end > self._segments[i].end: + possible_segments.append(i) + continue + possible_segments.append(i) + break + + # Check for the most overlapping option: + best_overlap = 0 + most_overlapping_segment_index = None + for i in possible_segments: + # If the word starts before segment: + if word.start <= self._segments[i].start: + # If it ends before the segment, there is an overlap from the start of the segment to the end of the + # word: + if word.end < self._segments[i].end: + overlap = word.end - self._segments[i].start + else: + # The word is wrapping the segment, the overlap is the segment's length: + overlap = self._segments[i].end - self._segments[i].start + # The word starts in segment, check if the word ends in it: + elif word.end < self._segments[i].end: + # The overlap is the word's length: + overlap = word.end - word.start + # The word start in segment but ends after it, the overlap is from the word's start to the segment's end: + else: + overlap = self._segments[i].end - word.start + # Check for new best overlap: + if overlap > best_overlap: + best_overlap = overlap + most_overlapping_segment_index = i + if most_overlapping_segment_index is not None: + self._last_chosen_index = most_overlapping_segment_index + return + + # If there is no overlapping segment, return the closest segment: + best_distance = None + closest_segment_index = None + for i in possible_segments: + distance = ( + word.start - self._segments[i].end + if word.start > self._segments[i].end + else self._segments[i].start - word.end + ) + if best_distance is None or distance < best_distance: + best_distance = distance + closest_segment_index = i + self._last_chosen_index = closest_segment_index
    + + + +
    +[docs] +class SpeechDiarizationPerChannelTask(BaseTask): + """ + A task to write the transcription to file with respect to a given speech diarization per channel. + """ + + class _WordTimestamp(NamedTuple): + """ + A word with its start and end timestamps and speaker label (channel the word was taken from). + """ + + start: float + end: float + speaker: str + text: str + + def __init__(self, audio_file: Path, text_file: Path): + """ + Initialize the task. + + :param audio_file: Path to the audio file that was transcribed. + :param text_file: Path to the text file to write the transcription to. + """ + super().__init__( + audio_file=audio_file, transcription_output={}, text_file=text_file + ) + self._transcription_output_channels: List[Tuple[str, dict]] = [] + + @property + def transcription_output_channels(self) -> List[Tuple[str, dict]]: + """ + Get the transcription output channels. + + :returns: The transcription output channels. + """ + return self._transcription_output_channels + +
    +[docs] + def do_task(self): + """ + Try to perform the task storing an error if occurred. + """ + for _, channel_output in self._transcription_output_channels: + if isinstance(channel_output, str): + self._error = self._transcription_output_channels + return + super().do_task()
    + + +
    +[docs] + def to_tuple(self) -> Tuple[str, dict]: + """ + Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + + :returns: The converted task. + """ + task_class, task_kwargs = super().to_tuple() + task_kwargs.pop("transcription_output") + return task_class, task_kwargs
    + + + def _do_task(self): + """ + Perform the task - write the transcription to the stored file path with respect to the given speech diarization + per channel. + """ + # Cast the chunks to word timestamps tuples: + words_per_channel = [ + [ + SpeechDiarizationPerChannelTask._WordTimestamp( + start=chunk["timestamp"][0], + end=chunk["timestamp"][1], + speaker=speaker, + text=chunk["text"], + ) + for chunk in output["chunks"] + ] + for speaker, output in self._transcription_output_channels + ] + + # Merge and sort the words per channel by their start time: + words = operator.add(*words_per_channel) + words.sort() + + # Write the transcription to file: + current_speaker = words[0].speaker + text = f"{current_speaker}:" + for word in words: + # Check if the word's speaker is different from the current one: + if word.speaker != current_speaker: + # Append a newline and update the new speaker: + current_speaker = word.speaker + text += f"\n{current_speaker}:" + # Collect the word: + text += word.text + + # Update the transcription output with the new text to write it to file: + self._transcription_output["text"] = text + super()._do_task()
    + + + +
    +[docs] +class BatchProcessor: + """ + A batch processor to process batches of transcriptions. The batch processor is creating tasks and is aimed to be + working along the transcriber. It can be used with multiprocessing queue or run the tasks directly using the + associated methods. + """ + + def __init__(self, audio_files: List[Path], output_directory: Path): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + """ + # Store the parameters: + self._audio_files = audio_files + self._output_directory = output_directory + + # Prepare the batching variables: + self._current_file_index = 0 + self._tasks: List[BaseTask] = [] + self._results: List[Tuple[bool, Tuple[str, str]]] = [] + +
    +[docs] + def process_batch(self, batch: List[Union[dict, str]]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Get the relevant files belongs to the given batch: + current_files = self._get_current_files(batch_size=len(batch)) + + # Build the diarization tasks: + self._tasks.extend( + [ + BaseTask( + audio_file=file, + transcription_output=batch[i], + text_file=self._output_directory / f"{file.stem}.txt", + ) + for i, file in enumerate(current_files) + ] + )
    + + +
    +[docs] + def get_tasks(self) -> List[BaseTask]: + """ + Get the tasks to perform. + + :returns: The tasks to perform. + """ + tasks = self._tasks + self._tasks = [] + return tasks
    + + +
    +[docs] + def do_tasks(self): + """ + Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber. + """ + for task in self.get_tasks(): + task.do_task() + self._results.append((task.is_failed(), task.get_result()))
    + + +
    +[docs] + def get_results(self) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Get the results of the tasks. The stored results are then cleared. + + :returns: The results of the tasks. + """ + results = self._results + self._results = [] + return results
    + + + def _get_current_files(self, batch_size: int) -> List[Path]: + """ + Get the current files to process. + + :param batch_size: The batch size to progress the current file index. + + :returns: The current files to process. + """ + end_index = ( + self._current_file_index + batch_size + if self._current_file_index + batch_size < len(self._audio_files) + else len(self._audio_files) + ) + current_files = self._audio_files[self._current_file_index : end_index] + self._current_file_index = end_index + return current_files
    + + + +
    +[docs] +class SpeechDiarizationBatchProcessor(BatchProcessor): + """ + A batch processor to process batches of transcriptions with respect to a given speech diarization. The batch + processor is creating tasks and is aimed to be working along the transcriber. It can be used with multiprocessing + queue or run the tasks directly using the associated methods. + """ + + def __init__( + self, audio_files: List[Path], output_directory: Path, speech_diarization: dict + ): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + :param speech_diarization: A speech diarization dictionary to pass along with each processed batch. + """ + super().__init__(audio_files=audio_files, output_directory=output_directory) + self._speech_diarization = speech_diarization + self._audio_files = audio_files + +
    +[docs] + def process_batch(self, batch: List[dict]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Get the relevant files belongs to the given batch: + current_files = self._get_current_files(batch_size=len(batch)) + + # Build the diarization tasks: + self._tasks.extend( + [ + SpeechDiarizationTask( + audio_file=file, + transcription_output=batch[i], + text_file=self._output_directory / f"{file.stem}.txt", + speech_diarization=self._speech_diarization.get(file.name), + ) + for i, file in enumerate(current_files) + ] + )
    +
    + + + +
    +[docs] +class PerChannelSpeechDiarizationBatchProcessor(BatchProcessor): + """ + A batch processor to process batches of transcriptions per channel. The batch processor is creating tasks with the + selected amount of channels given and is aimed to be working along the transcriber. It can be used with + multiprocessing queue or run the tasks directly using the associated methods. + """ + + def __init__( + self, + audio_files: List[Path], + output_directory: Path, + n_channels: int, + speakers: List[str], + ): + """ + Initialize the batch processor. + + :param audio_files: The list of all audio files to transcribe. + :param output_directory: The output directory to write the transcriptions to. + :param n_channels: The number of channels in each audio file to transcribe. + :param speakers: The speakers labels to use for each channel. + """ + super().__init__(audio_files=audio_files, output_directory=output_directory) + + # Store the parameters: + self._n_channels = n_channels + self._speakers = speakers + + # Prepare a channel buffer to store the channels until the current task created is fully covered: + self._task_in_process: SpeechDiarizationPerChannelTask = None + +
    +[docs] + def process_batch(self, batch: List[dict]): + """ + Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch + processor. + + :param batch: The batch of transcriptions to process. + """ + # Go over the batch and create the tasks: + for output in batch: + # Check if there is a task in process: + if not self._task_in_process: + # Create a new task: + self._task_in_process = SpeechDiarizationPerChannelTask( + audio_file=self._audio_files[self._current_file_index], + text_file=self._output_directory + / f"{self._audio_files[self._current_file_index].stem}.txt", + ) + # Get the channel's speaker: + speaker = self._speakers[ + len(self._task_in_process.transcription_output_channels) + ] + # Collect the channel into the processed task: + self._task_in_process.transcription_output_channels.append( + (speaker, output) + ) + # Check if the task is fully covered (all channels are collected): + if ( + len(self._task_in_process.transcription_output_channels) + == self._n_channels + ): + # Collect the task and reset the task in process: + self._tasks.append(self._task_in_process) + self._current_file_index += 1 + self._task_in_process = None
    +
    + + + +
    +[docs] +class Transcriber: + """ + A transcription wrapper for the Huggingface's ASR pipeline - + https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline to + use with OpenAI's Whisper models - https://huggingface.co/openai. + """ + + def __init__( + self, + model_name: str, + device: str = None, + use_flash_attention_2: bool = None, + use_better_transformers: bool = None, + assistant_model: str = None, + max_new_tokens: int = 128, + chunk_length_s: int = 30, + batch_size: int = 2, + spoken_language: str = None, + translate_to_english: bool = False, + return_timestamps: Union[bool, Literal["word"]] = False, + per_channel_transcription: int = 0, + ): + """ + Initialize the transcriber. + + :param model_name: The model name to use. Should be a model from the OpenAI's Whisper models for + best results (for example "tiny", "base", "large", etc.). + :param device: The device to use for inference. If not given, will use GPU if available. + :param use_flash_attention_2: Whether to use the Flash Attention 2 implementation. It can be used only with + one of the following GPUs: Nvidia H series and Nvidia A series. T4 support + will be available soon. + + Note: If both `use_flash_attention_2` and + `use_better_transformers` are `None`, the optimization will be chosen + automatically according to the available resources. + + :param use_better_transformers: Whether to use the Better Transformers library to further optimize the model. + Should be used for all use cases that do not support flash attention 2. + + Note: If both `use_flash_attention_2` and `use_better_transformers` are + `None`, the optimization will be chosen automatically according to the + available resources. + :param assistant_model: The assistant model name to use for inference. Notice that the optimizations + (flash attention 2 and better transformers) will be applied for the assistant + as well. Should be a model from Huggingface's distil-whisper (see here for + more information: https://github.com/huggingface/distil-whisper). + :param max_new_tokens: The maximum number of new tokens to generate. This is used to limit the + generation length. Default is 128 tokens. + :param chunk_length_s: The audio chunk to split the audio to (in seconds). Default is 30 seconds. + :param batch_size: The batch size to use for inference. Default is 2. + :param spoken_language: Aim whisper to know what language is spoken. If None, it will try to detect it + for each chunk. + :param translate_to_english: Whether to translate the transcriptions to English. Default is False. + :param return_timestamps: Whether to return the timestamps of the words. If "word", will return the + timestamps of each word. If True will return the timestamps of each chunk. + Default is False. Aimed to be used for speech diarization. + :param per_channel_transcription: Whether to do per channel transcription. If needed to run per channel + transcription, pass the number of channels expected for each audio file here. + 0 means regular transcription (merge channels). + + Note: If `per_channel_transcription` is not 0, `batch_size` must be treated to + be the number of channels and not audio files. Aimed to be used for per + channel speech diarization. + """ + # Store loading parameters: + self._model_name = model_name + self._device = device + self._use_flash_attention_2 = use_flash_attention_2 + self._use_better_transformers = use_better_transformers + self._max_new_tokens = max_new_tokens + self._chunk_length_s = chunk_length_s + self._batch_size = batch_size + self._return_timestamps = return_timestamps + self._per_channel_transcription = per_channel_transcription + + # Store generation parameters: + self._assistant_model = assistant_model + self._spoken_language = spoken_language + self._translate_to_english = translate_to_english + + # Prepare the transcription objects: + self._transcription_pipeline: AutomaticSpeechRecognitionPipeline = None + self._generate_kwargs: dict = None + +
    +[docs] + def load(self): + """ + Load the transcriber. Must be called before transcribing. + """ + # Set the device and data type to use (prefer GPU if available): + device = torch.device( + self._device or "cuda" if torch.cuda.is_available() else "cpu" + ) + torch_dtype = torch.float16 if device.type == "cuda" else torch.float32 + + # Choose the optimization to use (in case the user did not specify any): + if ( + self._use_flash_attention_2 is None + and self._use_better_transformers is None + ): + # Prefer to use flash attention 2 if available and cuda device is supported (see GPU names to architecture + # here: https://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units#Tesla): + if device.type == "cuda" and is_flash_attn_2_available(): + cuda_device_name = torch.cuda.get_device_properties(device).name + if any( + cuda_device_name.startswith(gpu_name) + for gpu_name in [ + "NVIDIA A", # For Ampere architecture (e.g. A10, A30, A100) + "NVIDIA H", # For Hopper architecture (e.g. H100) + "NVIDIA L", # For Ada Lovelace architecture (e.g. L4, L40) + "NVIDIA RTX 30", # For Ada Lovelace architecture (RTX 30 series) + "NVIDIA RTX 40", # For Ada Lovelace architecture (RTX 40 series) + "NVIDIA RTX 50", # For Ada Lovelace architecture (RTX 50 series) + # Will be supported soon according to FlashAttention GitHub repo: + # https://github.com/Dao-AILab/flash-attention?tab=readme-ov-file#installation-and-features + # "NVIDIA T4", # For Turing architecture (only T4) + # "NVIDIA RTX 20", # For Turing architecture (RTX 20 series) + ] + ): + self._use_flash_attention_2 = True + else: + self._use_better_transformers = True + else: + self._use_better_transformers = True + + # Build the optimizations kwargs: + model_kwargs = { + "low_cpu_mem_usage": True, + "use_safetensors": True, + } + if self._use_flash_attention_2: + if _LOGGER: + _LOGGER.info( + "Using FlashAttention2 optimization - make sure the `flash-attn` package is installed via " + "`pip install -U flash-attn --no-build-isolation`" + ) + model_kwargs["attn_implementation"] = "flash_attention_2" + elif self._use_better_transformers: + if _LOGGER: + _LOGGER.info( + "Using BetterTransformers optimization - make sure the `optimum` package is installed via " + "`pip install -U optimum`" + ) + model_kwargs["attn_implementation"] = "sdpa" + + # Initialize the speech recognition pipeline: + self._transcription_pipeline = pipeline( + task="automatic-speech-recognition", + model=self._model_name, + model_kwargs=model_kwargs.copy(), + batch_size=self._batch_size, + max_new_tokens=self._max_new_tokens, + chunk_length_s=self._chunk_length_s, + return_timestamps=self._return_timestamps, + torch_dtype=torch_dtype, + device=device, + ) + + # Prepare the generation kwargs: + self._generate_kwargs = { + "language": self._spoken_language, + "task": "translate" if self._translate_to_english else "transcribe", + } + + # Initialize the assistant model (if needed): + if self._assistant_model: + assistant_model = AutoModelForCausalLM.from_pretrained( + self._assistant_model, torch_dtype=torch_dtype, **model_kwargs + ) + assistant_model.to(device) + self._generate_kwargs["assistant_model"] = assistant_model
    + + +
    +[docs] + def transcribe( + self, + audio_files: List[Path], + batch_processor: BatchProcessor = None, + batches_queue: Queue = None, + verbose: bool = False, + ) -> Union[List[List[dict]], None]: + """ + Transcribe the given audio files. The transcriptions will be sent to a queue or a batch processor for further + processing like writing to text files. If no queue or batch processor is given, the transcriptions outputs from + the pipeline will be returned. Otherwise, `None` is returned. + + :param audio_files: The audio files to transcribe. + :param batch_processor: A batch processor. + :param batches_queue: A multiprocessing queue to put the batches in. + :param verbose: Whether to show a progress bar. Default is False. + + :returns: The transcriptions outputs from the pipeline if no queue or batch processor is given, otherwise, + `None`. + """ + # Wrap the audio files with a function to iterate over them via a generator (save memory and runtime with + # Huggingface's pipelines as they preload each input while inference is running): + def audio_iterator() -> Generator[Union[dict, str], None, None]: + if self._per_channel_transcription: + for audio_file in audio_files: + audio, sampling_rate = torchaudio.load(str(audio_file)) + audio = audio.numpy() + for channel in audio: + yield {"raw": channel, "sampling_rate": sampling_rate} + else: + for audio_file in audio_files: + yield str(audio_file) + + # Create a batch iterator: + def batch_iterator() -> Generator[List[Union[dict, str]], None, None]: + batch = [] + for audio in audio_iterator(): + batch.append(audio) + if len(batch) == self._batch_size: + yield batch + batch = [] + if batch: + yield batch + + # Prepare the successes dataframe and errors dictionary to be returned: + outputs = [] + + # Infer through the pipeline: + for input_batch in tqdm( + batch_iterator() if self._batch_size > 1 else audio_iterator(), + desc="Transcribing", + unit="channel" if self._per_channel_transcription else "audio file", + total=( + ( + (len(audio_files) // self._batch_size) + + (len(audio_files) % self._batch_size != 0) + ) + * (self._per_channel_transcription or 1) + ), + disable=not verbose, + ): + # Infer: + try: + output_batch = self._transcription_pipeline( + input_batch, + generate_kwargs=self._generate_kwargs, + ) + except Exception as exception: + # Collect the exception: + output_batch = str(exception) + # Align to batch size: + output_batch = ( + [output_batch] * len(input_batch) + if isinstance(input_batch, list) + else [output_batch] + ) + # To align with batching, if batch size is 1, wrap the output with a list: + if isinstance(output_batch, dict): + output_batch = [output_batch] + # If a batch processor is given, process the batch: + if batch_processor: + # Process it directly: + batch_processor.process_batch(batch=output_batch) + batch_processor.do_tasks() + elif batches_queue: + # Otherwise, queue the batch: + batches_queue.put(output_batch) + else: + # Otherwise, collect the output as is without processing: + outputs.append(output_batch) + + # Check if given a multiprocessing queue or a batch processor: + if batches_queue: + batches_queue.put(_MULTIPROCESSING_STOP_MARK) + + return outputs if not batch_processor else None
    +
    + + + +#: The value to send into multiprocessing queues to stop the process: +_MULTIPROCESSING_STOP_MARK = "STOP" + + +def _multiprocessing_process_batches( + batch_processor: BatchProcessor, + batches_queue: Queue, + tasks_queue: Queue, + n_task_completers: int, +): + """ + Process the batches in the given batches queue and put the tasks in the given tasks queue. The function will stop + when the given batches queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param batch_processor: A batch processor to process the batches. + :param batches_queue: A queue to get the batches from. + :param tasks_queue: A queue to put the tasks in. + :param n_task_completers: The number of task completers (processes that run the `_multiprocessing_complete_tasks` + function). A stop mark will be sent to the tasks queue for each task completer. + """ + while True: + # Get the batch: + batch: List[dict] = batches_queue.get() + if batch == _MULTIPROCESSING_STOP_MARK: + break + + # Process the batch: + batch_processor.process_batch(batch=batch) + + # Get the tasks: + tasks = batch_processor.get_tasks() + + # Queue the tasks: + for task in tasks: + tasks_queue.put(task.to_tuple()) + + # Mark the end of the batches: + for _ in range(n_task_completers): + tasks_queue.put(_MULTIPROCESSING_STOP_MARK) + + +def _multiprocessing_complete_tasks(tasks_queue: Queue, results_queue: Queue): + """ + Complete the tasks in the given queue and put the results in the given results queue. The function will stop when + the given tasks queue will receive the stop mark. It is aimed to be used with multiprocessing as a process. + + :param tasks_queue: A queue to get the tasks from. + :param results_queue: A queue to put the results in. + """ + tasks_map = { + BaseTask.__name__: BaseTask, + SpeechDiarizationTask.__name__: SpeechDiarizationTask, + SpeechDiarizationPerChannelTask.__name__: SpeechDiarizationPerChannelTask, + } + + while True: + # Get the task: + task = tasks_queue.get() + if task == _MULTIPROCESSING_STOP_MARK: + break + + # Reconstruct the task: + task_class, task_kwargs = task + task = tasks_map[task_class](**task_kwargs) + + # Complete the task: + task.do_task() + results_queue.put((task.is_failed(), task.get_result())) + + # Mark the end of the tasks: + results_queue.put(_MULTIPROCESSING_STOP_MARK) + + +# Get the global logger: +_LOGGER = logging.getLogger() + + +
    +[docs] +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, str): + input_argument = _get_audio_files( + data_path=Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Save the output directory of this worker: + output_directory = Path(output[0]) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + + # Join the data from all workers: + if rank == 0: + context.logger.info("Collecting data from workers to root worker.") + + # Check if there are different output directories: + output_directories = set([Path(out_dir) for out_dir, _, _ in output]) + for r in range(1, size): + # True means the other workers should pass their files to the root worker (rank 0): + comm.send(len(output_directories) != 1, dest=r) + + # If there are different output directories, listen to the other workers: + if len(output_directories) != 1: + # Collect the files from the other workers: + files = [] + for r in range(1, size): + files.extend(comm.recv(source=r)) + # Write the files to the root worker's output directory: + for file_name, file_content in files: + with open(output_directory / file_name, "w") as f: + f.write(file_content) + + # Concatenate the dataframes: + dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0) + + # Concatenate the errors dictionaries: + errors_dictionary = reduce( + operator.ior, [err for _, _, err in output], {} + ) + + return str(output_directory), dataframe, errors_dictionary + + # Listen to rank 0 to see if there are different output directories and this rank need to send its files to + # it: + if comm.recv(source=0): + files = [] + for file in os.listdir(output_directory): + with open(output_directory / file, "r") as f: + files.append((file, f.read())) + comm.send(files, dest=0) + return None + + return wrapper + + return decorator
    + + + +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]: + is_mpi = False + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + is_mpi = context.labels.get("kind", "job") == "mpijob" + + if is_mpi: + try: + from mpi4py import MPI + + return context, MPI.COMM_WORLD + except ModuleNotFoundError as mpi4py_not_found: + context.logger.error( + "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your " + "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi." + ) + raise mpi4py_not_found + else: + return context, None + except ModuleNotFoundError as module_not_found: + if is_mpi: + raise module_not_found + return None, None + + +
    +[docs] +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def transcribe( + # Input / Output kwargs: + data_path: Union[str, Path, List[Union[str, Path]]], + output_directory: str = None, + # Model loading kwargs: + model_name: str = "openai/whisper-tiny", + device: str = None, + use_flash_attention_2: bool = None, + use_better_transformers: bool = None, + # Generation kwargs: + assistant_model: str = None, + max_new_tokens: int = 128, + chunk_length_s: int = 30, + batch_size: int = 8, + spoken_language: str = None, + translate_to_english: bool = False, + # Diarization kwargs: + speech_diarization: Dict[str, List[Tuple[float, float, str]]] = None, + speech_diarize_per_channel: int = None, + speaker_labels: List[str] = None, + # Other kwargs: + use_multiprocessing: Union[bool, int] = False, + verbose: bool = False, +): + """ + Transcribe audio files into text files and collect additional data. The end result is a directory of transcribed + text files and a dataframe containing the following columns: + + * audio_file - The audio file path. + * transcription_file - The transcribed text file name in the output directory. + + The transcription is based on Huggingface's ASR pipeline - + https://huggingface.co/transformers/main_classes/pipelines.html#transformers.AutomaticSpeechRecognitionPipeline and + is tested with OpenAI's Whisper models - https://huggingface.co/openai. + + If one of the speaker diarization parameters are given (either `speech_diarization` or + `speech_diarize_per_channel`), the transcription will be written in a conversation format, where each speaker will + be written in a separate line:: + + speaker_1: text + speaker_2: text + speaker_1: text + ... + + :param data_path: A directory of audio files or a single file or a list of files to transcribe. + :param output_directory: Path to a directory to save all transcribed audio files. If not given, will save + the transcribed files in a temporary directory. + :param model_name: The model name to use. Should be a model from the OpenAI's Whisper models for + best results (for example "tiny", "base", "large", etc.). See here for more + information: https://huggingface.co/openai?search_models=whisper. + :param device: The device to use for inference. If not given, will use GPU if available. + :param use_flash_attention_2: Whether to use the Flash Attention 2 implementation. It can be used only with + one of the following GPUs: Nvidia H series and Nvidia A series. T4 support + will be available soon. + + Note: If both `use_flash_attention_2` and + `use_better_transformers` are `None`, the optimization will be chosen + automatically according to the available resources. + + :param use_better_transformers: Whether to use the Better Transformers library to further optimize the model. + Should be used for all use cases that do not support flash attention 2. + + Note: If both `use_flash_attention_2` and `use_better_transformers` are + `None`, the optimization will be chosen automatically according to the + available resources. + :param assistant_model: The assistant model name to use for inference. Notice that the optimizations + (flash attention 2 and better transformers) will be applied for the assistant as + well. Should be a model from Huggingface's distil-whisper (see here for more + information: https://github.com/huggingface/distil-whisper). + + Note: Currently an assistant model is only usable with batch size of 1. + :param max_new_tokens: The maximum number of new tokens to generate. This is used to limit the + generation length. Default is 128 tokens. + :param chunk_length_s: The audio chunk to split the audio to (in seconds). Default is 30 seconds. + :param batch_size: The batch size to use for inference. Default is 2. + :param spoken_language: Aim whisper to know what language is spoken. If None, it will try to detect + it. + :param translate_to_english: Whether to translate the transcriptions to English. + :param speech_diarization: A speech diarization dictionary with the file names to transcribe as keys and + their diarization as value. The diarization is a list of tuples: + (start, end, speaker). An example + for a diarization dictionary:: + + { + "audio_file_name": [ + { + "start": 0.0, + "end": 2.0, + "speaker": "Agent", + }, + { + "start": 2.0, + "end": 4.0, + "speaker": "Client", + }, + ... + ], + ... + } + + Note: The diarization must be for the entire duration of the audio file (as long + as Whisper is predicting words up until then. + :param speech_diarize_per_channel: Perform speech diarization per channel. Each speaker is expected to belong to + a separate channel in the audio. Notice: This will make the transcription + slower as each channel wil be transcribed separatly. If a speech diarization + is passed (via the `speech_diarization` parameter), this parameter is + ignored. + :param speaker_labels: A list of speaker labels by channel order to use for writing the + transcription with respect to per channel speech diarization. This won't be + used together with a given speech diarization (via the `speech_diarization` + parameter). + :param use_multiprocessing: Whether to use multiprocessing to transcribe the audio files. Can be either a + boolean value or an integer. If `True`, will use the default amount of workers + (3): 1 for transcription, 1 for batch processing and 1 for task completion (such + as speech diarization and writing to files). To control the amount of tasks + completion workers, an integer can be provided to specify the amount of workers. + `False`, will use a single process. Default is `False`. + :param verbose: Whether to print the progress of the transcription. Default is `False`. + """ + global _LOGGER + + # Get the input audio files to transcribe: + if verbose: + _LOGGER.info("Collecting audio files.") + audio_files = _get_audio_files(data_path=data_path) + if verbose: + _LOGGER.info(f"Collected {len(audio_files)} audio files.") + + # Get the output directory: + if output_directory is None: + if verbose: + _LOGGER.info("No output directory given, using temporary directory.") + output_directory = tempfile.mkdtemp() + output_directory = Path(output_directory).absolute() + output_directory.mkdir(exist_ok=True, parents=True) + if verbose: + _LOGGER.info(f"Transcriptions will be saved to: {output_directory}") + + # Initialize a batch processor according to user requirements (no speech diarization, given speech diarization, + # speech diarization per channel): + if speech_diarization: + batch_processor = SpeechDiarizationBatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + speech_diarization=speech_diarization, + ) + elif speech_diarize_per_channel: + batch_processor = PerChannelSpeechDiarizationBatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + n_channels=speech_diarize_per_channel, + speakers=speaker_labels, + ) + else: + batch_processor = BatchProcessor( + audio_files=audio_files, + output_directory=output_directory, + ) + + # Initialize the transcription pipeline: + transcriber = Transcriber( + device=device, + use_flash_attention_2=use_flash_attention_2, + use_better_transformers=use_better_transformers, + assistant_model=assistant_model, + model_name=model_name, + max_new_tokens=max_new_tokens, + chunk_length_s=chunk_length_s, + batch_size=batch_size, + return_timestamps=( + "word" + if speech_diarization is not None or speech_diarize_per_channel is not None + else False + ), + per_channel_transcription=speech_diarize_per_channel or 0, + spoken_language=spoken_language, + translate_to_english=translate_to_english, + ) + + # Run the transcription: + if use_multiprocessing: + results = _parallel_run( + n_workers=use_multiprocessing + if isinstance(use_multiprocessing, int) + else 1, + audio_files=audio_files, + batch_processor=batch_processor, + transcriber=transcriber, + verbose=verbose, + ) + else: + results = _run( + audio_files=audio_files, + batch_processor=batch_processor, + transcriber=transcriber, + verbose=verbose, + ) + + # Process the results: + if verbose: + _LOGGER.info("Summarizing the results.") + successes = [] + errors = {} + for is_error, result in results: + if is_error: + errors[result[0]] = result[1] + else: + successes.append(result) + successes = pd.DataFrame(successes, columns=["audio_file", "transcription_file"]) + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(audio_files)})\n" + f"Transcriptions summary:\n" + f"{successes.head()}" + ) + + return str(output_directory), successes, errors
    + + + +def _get_audio_files( + data_path: Union[Path, str, list], +) -> List[Path]: + """ + Get the audio files to transcribe. If a path to a directory is given, all files in the directory will be collected. + + :param data_path: The data path to collect the audio files from. + + :returns: The audio files list. + """ + # Check if given a list of paths: + if isinstance(data_path, list): + audio_files = [] + for path in data_path: + audio_files.extend(_get_audio_files(data_path=path)) + return audio_files + + # Check if given a single string path to cast it to a `pathlib.Path`: + if isinstance(data_path, str): + data_path = Path(data_path).absolute() + + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + audio_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + audio_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be a valid path to either a directory path or a " + f"file. Given: {str(data_path)} " + ) + + return audio_files + + +def _run( + audio_files: List[Path], + batch_processor: BatchProcessor, + transcriber: Transcriber, + verbose: bool, +) -> List[Tuple[bool, Tuple[str, str]]]: + """ + Run the transcription without multiprocessing. + + :param audio_files: The audio files to transcribe. + :param batch_processor: The batch processor to use. + :param transcriber: The transcriber to use. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Load the transcription pipeline: + if verbose: + _LOGGER.info(f"Loading the transcription pipeline.") + transcriber.load() + if verbose: + _LOGGER.info("Transcription pipeline loaded.") + + # Transcribe the files: + transcriber.transcribe( + audio_files=audio_files, + batch_processor=batch_processor, + verbose=verbose, + ) + + # Return the results: + return batch_processor.get_results() + + +def _parallel_run( + n_workers: int, + audio_files: List[Path], + batch_processor: BatchProcessor, + transcriber: Transcriber, + verbose: bool, +): + """ + Run the transcription with multiprocessing. + + :param n_workers: The amount of workers to use as task completers. + :param audio_files: The audio files to transcribe. + :param batch_processor: The batch processor to use. + :param transcriber: The transcriber to use. + :param verbose: Verbosity. + + :returns: The collected results. + """ + # Initialize the multiprocessing queues: + batches_queue = Queue() + tasks_queue = Queue() + results_queue = Queue() + + # Initialize the multiprocessing processes: + batch_processing_process = Process( + target=_multiprocessing_process_batches, + kwargs={ + "batch_processor": batch_processor, + "batches_queue": batches_queue, + "tasks_queue": tasks_queue, + "n_task_completers": n_workers, + }, + ) + task_completion_processes = [ + Process( + target=_multiprocessing_complete_tasks, + kwargs={"tasks_queue": tasks_queue, "results_queue": results_queue}, + ) + for _ in range(n_workers) + ] + + # Start the multiprocessing processes: + batch_processing_process.start() + for p in task_completion_processes: + p.start() + + # Load the transcription pipeline: + if verbose: + _LOGGER.info(f"Loading the transcription pipeline.") + transcriber.load() + if verbose: + _LOGGER.info("Transcription pipeline loaded.") + + # Transcribe the files: + transcriber.transcribe( + audio_files=audio_files, batches_queue=batches_queue, verbose=verbose + ) + + # Collect the results: + results = [] + stop_marks_counter = 0 + while True: + # Get a result from the queue: + result: Tuple[bool, Tuple[str, str]] = results_queue.get() + if result == _MULTIPROCESSING_STOP_MARK: + stop_marks_counter += 1 + if stop_marks_counter == n_workers: + break + else: + # Collect the result: + results.append(result) + + # Wait for the processes to finish: + results_queue.empty() + batch_processing_process.join() + for p in task_completion_processes: + p.join() + + return results +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/transcribe/latest/src/function.yaml b/functions/master/transcribe/latest/src/function.yaml index d72751ad..43e9b3a8 100644 --- a/functions/master/transcribe/latest/src/function.yaml +++ b/functions/master/transcribe/latest/src/function.yaml @@ -1,25 +1,13 @@ kind: job metadata: - name: transcribe - tag: '' - hash: 8810ac74045bd15cee15a2e4e89563e8e29908d3 - project: '' - labels: - author: yonatans categories: - - data-preparation + - audio - genai - - huggingface - - machine-learning + tag: '' + name: transcribe +verbose: false spec: - command: '' - args: [] - image: '' build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz - base_image: mlrun/mlrun - commands: [] - code_origin: '' origin_filename: '' requirements: - transformers @@ -27,118 +15,122 @@ spec: - torchaudio - torch - accelerate + base_image: mlrun/mlrun + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz + disable_auto_mount: false + description: Transcribe audio files into text files + image: '' + command: '' + default_handler: transcribe entry_points: do_task: name: do_task doc: Try to perform the task storing an error if occurred. + lineno: 348 parameters: - name: self - outputs: [] - lineno: 348 has_varargs: false has_kwargs: false is_failed: name: is_failed doc: Check if the task failed. + lineno: 70 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: Whether the task failed. type: bool - lineno: 70 - has_varargs: false - has_kwargs: false get_result: name: get_result doc: 'Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the text file name.' + lineno: 78 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The task's result. type: Tuple[str, str] - lineno: 78 - has_varargs: false - has_kwargs: false to_tuple: name: to_tuple doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + lineno: 358 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The converted task. type: Tuple[str, dict] - lineno: 358 - has_varargs: false - has_kwargs: false transcription_output_channels: name: transcription_output_channels doc: Get the transcription output channels. + lineno: 340 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The transcription output channels. type: List[Tuple[str, dict]] - lineno: 340 - has_varargs: false - has_kwargs: false process_batch: name: process_batch doc: 'Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch processor.' + lineno: 575 parameters: - name: self - name: batch type: List[dict] doc: The batch of transcriptions to process. - outputs: [] - lineno: 575 has_varargs: false has_kwargs: false get_tasks: name: get_tasks doc: Get the tasks to perform. + lineno: 453 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The tasks to perform. type: List[BaseTask] - lineno: 453 - has_varargs: false - has_kwargs: false do_tasks: name: do_tasks doc: Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber. + lineno: 463 parameters: - name: self - outputs: [] - lineno: 463 has_varargs: false has_kwargs: false get_results: name: get_results doc: Get the results of the tasks. The stored results are then cleared. + lineno: 471 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The results of the tasks. type: List[Tuple[bool, Tuple[str, str]]] - lineno: 471 - has_varargs: false - has_kwargs: false load: name: load doc: Load the transcriber. Must be called before transcribing. + lineno: 695 parameters: - name: self - outputs: [] - lineno: 695 has_varargs: false has_kwargs: false transcribe: @@ -154,6 +146,7 @@ spec: \ a conversation format, where each speaker will\nbe written in a separate\ \ line::\n\n speaker_1: text\n speaker_2: text\n speaker_1: text\n\ \ ..." + lineno: 1097 parameters: - name: data_path type: Union[str, Path, List[Union[str, Path]]] @@ -246,66 +239,47 @@ spec: type: bool doc: Whether to print the progress of the transcription. Default is `False`. default: false - outputs: [] - lineno: 1097 has_varargs: false has_kwargs: false audio_iterator: name: audio_iterator doc: '' - parameters: [] - outputs: - - type: Generator[Union[dict, str], None, None] lineno: 804 has_varargs: false has_kwargs: false + outputs: + - type: Generator[Union[dict, str], None, None] batch_iterator: name: batch_iterator doc: '' - parameters: [] - outputs: - - type: Generator[List[Union[dict, str]], None, None] lineno: 816 has_varargs: false has_kwargs: false + outputs: + - type: Generator[List[Union[dict, str]], None, None] open_mpi_handler: name: open_mpi_handler doc: '' + lineno: 957 parameters: - name: worker_inputs type: List[str] - name: root_worker_inputs type: Dict[str, Any] default: null - outputs: [] - lineno: 957 has_varargs: false has_kwargs: false decorator: name: decorator doc: '' + lineno: 969 parameters: - name: handler - outputs: [] - lineno: 969 has_varargs: false has_kwargs: false wrapper: name: wrapper doc: '' - parameters: [] - outputs: [] lineno: 974 has_varargs: false has_kwargs: true - description: Transcribe audio files into text files - default_handler: transcribe - disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false diff --git a/functions/master/transcribe/latest/src/item.yaml b/functions/master/transcribe/latest/src/item.yaml index 7fddcf95..6deaf710 100644 --- a/functions/master/transcribe/latest/src/item.yaml +++ b/functions/master/transcribe/latest/src/item.yaml @@ -1,9 +1,7 @@ apiVersion: v1 categories: -- data-preparation +- audio - genai -- huggingface -- machine-learning description: Transcribe audio files into text files doc: '' example: transcribe.ipynb @@ -14,7 +12,7 @@ labels: author: yonatans maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.1 +mlrunVersion: 1.7.0 name: transcribe platformVersion: 3.5.3 spec: @@ -29,4 +27,4 @@ spec: - torch - accelerate url: '' -version: 1.1.0 \ No newline at end of file +version: 1.2.0 \ No newline at end of file diff --git a/functions/master/transcribe/latest/static/documentation.html b/functions/master/transcribe/latest/static/documentation.html index 80bf639b..d92df103 100644 --- a/functions/master/transcribe/latest/static/documentation.html +++ b/functions/master/transcribe/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/transcribe/latest/static/example.html b/functions/master/transcribe/latest/static/example.html index f25f86a8..261d8df0 100644 --- a/functions/master/transcribe/latest/static/example.html +++ b/functions/master/transcribe/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/transcribe/latest/static/function.html b/functions/master/transcribe/latest/static/function.html index 852a901f..1f55b3ab 100644 --- a/functions/master/transcribe/latest/static/function.html +++ b/functions/master/transcribe/latest/static/function.html @@ -30,26 +30,14 @@ kind: job metadata: - name: transcribe - tag: '' - hash: 8810ac74045bd15cee15a2e4e89563e8e29908d3 - project: '' - labels: - author: yonatans categories: - - data-preparation + - audio - genai - - huggingface - - machine-learning + tag: '' + name: transcribe +verbose: false spec: - command: '' - args: [] - image: '' build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz - base_image: mlrun/mlrun - commands: [] - code_origin: '' origin_filename: '' requirements: - transformers @@ -57,118 +45,122 @@ - torchaudio - torch - accelerate + base_image: mlrun/mlrun + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyNCBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgppbXBvcnQgbG9nZ2luZwppbXBvcnQgb3BlcmF0b3IKaW1wb3J0IG9zCmltcG9ydCB0ZW1wZmlsZQpmcm9tIGZ1bmN0b29scyBpbXBvcnQgcmVkdWNlLCB3cmFwcwpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUHJvY2VzcywgUXVldWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgTGl0ZXJhbCwgTmFtZWRUdXBsZSwgVHVwbGUsIFVuaW9uCgppbXBvcnQgcGFuZGFzIGFzIHBkCmltcG9ydCB0b3JjaAppbXBvcnQgdG9yY2hhdWRpbwpmcm9tIHRxZG0gaW1wb3J0IHRxZG0KZnJvbSB0cmFuc2Zvcm1lcnMgaW1wb3J0ICgKICAgIEF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUsCiAgICBBdXRvTW9kZWxGb3JDYXVzYWxMTSwKICAgIHBpcGVsaW5lLAopCmZyb20gdHJhbnNmb3JtZXJzLnV0aWxzIGltcG9ydCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlCgoKY2xhc3MgQmFzZVRhc2s6CiAgICAiIiIKICAgIEEgdGFzayB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKAogICAgICAgIHNlbGYsIGF1ZGlvX2ZpbGU6IFBhdGgsIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBVbmlvbltkaWN0LCBzdHJdLCB0ZXh0X2ZpbGU6IFBhdGgKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdGFzay4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGU6ICAgICAgICAgICBQYXRoIHRvIHRoZSBhdWRpbyBmaWxlIHRoYXQgd2FzIHRyYW5zY3JpYmVkLgogICAgICAgIDpwYXJhbSB0cmFuc2NyaXB0aW9uX291dHB1dDogVGhlIHRyYW5zY3JpcHRpb24gb3V0cHV0IGZyb20gdGhlIHBpcGVsaW5lLiBTdHJpbmcgbWVhbnMgYW4gZXhjZXB0aW9uIHdhcyByYWlzZWQuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgIiIiCiAgICAgICAgIyBTdG9yZSB0aGUgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9hdWRpb19maWxlID0gYXVkaW9fZmlsZQogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0ID0gdHJhbnNjcmlwdGlvbl9vdXRwdXQKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUgPSB0ZXh0X2ZpbGUKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBlcnJvciB2YXJpYWJsZToKICAgICAgICBzZWxmLl9lcnJvcjogc3RyID0gTm9uZQoKICAgIGRlZiBkb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFRyeSB0byBwZXJmb3JtIHRoZSB0YXNrIHN0b3JpbmcgYW4gZXJyb3IgaWYgb2NjdXJyZWQuCiAgICAgICAgIiIiCiAgICAgICAgaWYgaXNpbnN0YW5jZShzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dCwgc3RyKToKICAgICAgICAgICAgc2VsZi5fZXJyb3IgPSBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dAogICAgICAgICAgICByZXR1cm4KICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuX2RvX3Rhc2soKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICBzZWxmLl9lcnJvciA9IHN0cihleGNlcHRpb24pCgogICAgZGVmIGlzX2ZhaWxlZChzZWxmKSAtPiBib29sOgogICAgICAgICIiIgogICAgICAgIENoZWNrIGlmIHRoZSB0YXNrIGZhaWxlZC4KCiAgICAgICAgOnJldHVybnM6IFdoZXRoZXIgdGhlIHRhc2sgZmFpbGVkLgogICAgICAgICIiIgogICAgICAgIHJldHVybiBzZWxmLl9lcnJvciBpcyBub3QgTm9uZQoKICAgIGRlZiBnZXRfcmVzdWx0KHNlbGYpIC0+IFR1cGxlW3N0ciwgc3RyXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIHJlc3VsdCBvZiB0aGUgdGFzay4gSWYgdGhlIHRhc2sgZmFpbGVkLCB0aGUgZXJyb3Igd2lsbCBiZSByZXR1cm5lZCwgb3RoZXJ3aXNlLCB0aGUgcmVzdWx0IHdpbGwgYmUgdGhlCiAgICAgICAgdGV4dCBmaWxlIG5hbWUuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdGFzaydzIHJlc3VsdC4KICAgICAgICAiIiIKICAgICAgICBpZiBzZWxmLmlzX2ZhaWxlZCgpOgogICAgICAgICAgICByZXR1cm4gc2VsZi5fYXVkaW9fZmlsZS5uYW1lLCBzZWxmLl9lcnJvcgogICAgICAgIHJldHVybiBzZWxmLl9hdWRpb19maWxlLm5hbWUsIHNlbGYuX3RleHRfZmlsZS5uYW1lCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgcmV0dXJuIHNlbGYuX19jbGFzc19fLl9fbmFtZV9fLCB7CiAgICAgICAgICAgICJhdWRpb19maWxlIjogc2VsZi5fYXVkaW9fZmlsZSwKICAgICAgICAgICAgInRyYW5zY3JpcHRpb25fb3V0cHV0Ijogc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXQsCiAgICAgICAgICAgICJ0ZXh0X2ZpbGUiOiBzZWxmLl90ZXh0X2ZpbGUsCiAgICAgICAgfQoKICAgIGRlZiBfZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBQZXJmb3JtIHRoZSB0YXNrIC0gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gdGhlIHN0b3JlZCBmaWxlIHBhdGguCiAgICAgICAgIiIiCiAgICAgICAgIyBDaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zOgogICAgICAgIGkgPSAxCiAgICAgICAgd2hpbGUgc2VsZi5fdGV4dF9maWxlLmV4aXN0cygpOgogICAgICAgICAgICBpICs9IDEKICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlID0gKAogICAgICAgICAgICAgICAgc2VsZi5fdGV4dF9maWxlLnBhcmVudAogICAgICAgICAgICAgICAgLyBmIntzZWxmLl90ZXh0X2ZpbGUuc3RlbS5yc3BsaXQoJ18nLCAxKVswXX1fe2l9e3NlbGYuX3RleHRfZmlsZS5zdWZmaXh9IgogICAgICAgICAgICApCgogICAgICAgICMgTWFrZSBzdXJlIGFsbCBkaXJlY3RvcmllcyBhcmUgY3JlYXRlZDoKICAgICAgICBzZWxmLl90ZXh0X2ZpbGUucGFyZW50Lm1rZGlyKGV4aXN0X29rPVRydWUsIHBhcmVudHM9VHJ1ZSkKCiAgICAgICAgIyBXcml0ZSB0byBmaWxlOgogICAgICAgIHdpdGggb3BlbihzZWxmLl90ZXh0X2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgICAgIGZwLndyaXRlKHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0pCgoKY2xhc3MgU3BlZWNoRGlhcml6YXRpb25UYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLgogICAgIiIiCgogICAgY2xhc3MgX0RpYXJpemF0aW9uU2VnbWVudChOYW1lZFR1cGxlKToKICAgICAgICAiIiIKICAgICAgICBBIHNwZWVjaCBkaWFyaXphdGlvbiBzZWdtZW50LgogICAgICAgICIiIgoKICAgICAgICBzdGFydDogZmxvYXQKICAgICAgICBlbmQ6IGZsb2F0CiAgICAgICAgc3BlYWtlcjogc3RyCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcy4KICAgICAgICAiIiIKCiAgICAgICAgc3RhcnQ6IGZsb2F0CiAgICAgICAgZW5kOiBmbG9hdAogICAgICAgIHRleHQ6IHN0cgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGU6IFBhdGgsCiAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ6IGRpY3QsCiAgICAgICAgdGV4dF9maWxlOiBQYXRoLAogICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbjogTGlzdFtUdXBsZVtmbG9hdCwgZmxvYXQsIHN0cl1dLAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogICAgICAgICAgIFBhdGggdG8gdGhlIGF1ZGlvIGZpbGUgdGhhdCB3YXMgdHJhbnNjcmliZWQuCiAgICAgICAgOnBhcmFtIHRyYW5zY3JpcHRpb25fb3V0cHV0OiBUaGUgdHJhbnNjcmlwdGlvbiBvdXRwdXQgZnJvbSB0aGUgcGlwZWxpbmUuCiAgICAgICAgOnBhcmFtIHRleHRfZmlsZTogICAgICAgICAgICBQYXRoIHRvIHRoZSB0ZXh0IGZpbGUgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8uCiAgICAgICAgOnBhcmFtIHNwZWVjaF9kaWFyaXphdGlvbjogICBBIHNwZWVjaCBkaWFyaXphdGlvbiBhcyBhIGxpc3Qgb2YgdHVwbGVzOiAoc3RhcnQsIGVuZCwgc3BlYWtlcikuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXygKICAgICAgICAgICAgYXVkaW9fZmlsZT1hdWRpb19maWxlLAogICAgICAgICAgICB0cmFuc2NyaXB0aW9uX291dHB1dD10cmFuc2NyaXB0aW9uX291dHB1dCwKICAgICAgICAgICAgdGV4dF9maWxlPXRleHRfZmlsZSwKICAgICAgICApCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fc2VnbWVudHM6IExpc3RbU3BlZWNoRGlhcml6YXRpb25UYXNrLl9EaWFyaXphdGlvblNlZ21lbnRdID0gTm9uZQogICAgICAgIHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ID0gMAoKICAgIGRlZiB0b190dXBsZShzZWxmKSAtPiBUdXBsZVtzdHIsIGRpY3RdOgogICAgICAgICIiIgogICAgICAgIENvbnZlcnQgdGhlIHRhc2sgdG8gYSB0dXBsZSB0byByZWNvbnN0cnVjdCBpdCBsYXRlciAodXNlZCBmb3IgbXVsdGlwcm9jZXNzaW5nIHRvIHBhc3MgaW4gcXVldWUpLgoKICAgICAgICA6cmV0dXJuczogVGhlIGNvbnZlcnRlZCB0YXNrLgogICAgICAgICIiIgogICAgICAgIHRhc2tfY2xhc3MsIHRhc2tfa3dhcmdzID0gc3VwZXIoKS50b190dXBsZSgpCiAgICAgICAgcmV0dXJuIHRhc2tfY2xhc3MsIHsKICAgICAgICAgICAgKip0YXNrX2t3YXJncywKICAgICAgICAgICAgInNwZWVjaF9kaWFyaXphdGlvbiI6IHNlbGYuX3NwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICB9CgogICAgZGVmIF9kb190YXNrKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2sgLSB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byB0aGUgc3RvcmVkIGZpbGUgcGF0aCB3aXRoIHJlc3BlY3QgdG8gdGhlIGdpdmVuIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIENoZWNrIGlmIGEgc3BlZWNoIGRpYXJpemF0aW9uIGlzIGdpdmVuLCBpZiBub3QsIGp1c3Qgd3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICBpZiBub3Qgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uOgogICAgICAgICAgICBzdXBlcigpLl9kb190YXNrKCkKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgQ2FzdCB0aGUgY2h1bmtzIHRvIHdvcmQgdGltZXN0YW1wcyB0dXBsZXM6CiAgICAgICAgd29yZHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgIHN0YXJ0PWNodW5rWyJ0aW1lc3RhbXAiXVswXSwKICAgICAgICAgICAgICAgIGVuZD1jaHVua1sidGltZXN0YW1wIl1bMV0sCiAgICAgICAgICAgICAgICB0ZXh0PWNodW5rWyJ0ZXh0Il0sCiAgICAgICAgICAgICkKICAgICAgICAgICAgZm9yIGNodW5rIGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJjaHVua3MiXQogICAgICAgIF0KCiAgICAgICAgIyBDYXN0IHNwZWVjaCBkaWFyaXphdGlvbiB0byBzZWdtZW50cyB0dXBsZXM6CiAgICAgICAgc2VsZi5fc2VnbWVudHMgPSBbCiAgICAgICAgICAgIFNwZWVjaERpYXJpemF0aW9uVGFzay5fRGlhcml6YXRpb25TZWdtZW50KCpzZWdtZW50KQogICAgICAgICAgICBmb3Igc2VnbWVudCBpbiBzZWxmLl9zcGVlY2hfZGlhcml6YXRpb24KICAgICAgICBdCgogICAgICAgICMgVHJ5IHRvIG1hdGNoIHRoZSBXaGlzcGVyIG1vZGVsIHByZWRpY3RlZCB0aW1lc3RhbXBzIHRvIHRoZSBjbG9zZXN0IGRpYXJpemF0aW9uIHNlZ21lbnQgKGNsb3Nlc3QgZGlhcml6YXRpb24KICAgICAgICAjIHNlZ21lbnQgd2lsbCBiZSB0aGUgbW9zdCBvdmVybGFwcGluZyB3aXRoIHRoZSB3b3JkLCBhbmQgaWYgdGhlcmUgaXMgbm8gb3ZlcmxhcCwgdGhlIGNsb3Nlc3Qgc2VnbWVudCB0byB0aGUKICAgICAgICAjIHdvcmQpOgogICAgICAgIHNwZWFrZXIgPSBzZWxmLl9zZWdtZW50c1tzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleF0uc3BlYWtlcgogICAgICAgIHRleHQgPSBmIntzcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgR2V0IHRoZSBuZXh0IGRpYXJpemF0aW9uIHNlZ21lbnQ6CiAgICAgICAgICAgIHNlbGYuX2dldF9uZXh0X3NlZ21lbnQod29yZD13b3JkKQogICAgICAgICAgICAjIENoZWNrIGlmIHRoZSBzZWdtZW50IGlzIG9mIHRoZSBzYW1lIHNwZWFrZXI6CiAgICAgICAgICAgIGlmIHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyID09IHNwZWFrZXI6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgICAgICB0ZXh0ICs9IHdvcmQudGV4dAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyBBcHBlbmQgYSBuZXdsaW5lIGFuZCB1cGRhdGUgdGhlIG5ldyBzcGVha2VyOgogICAgICAgICAgICAgICAgc3BlYWtlciA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XS5zcGVha2VyCiAgICAgICAgICAgICAgICB0ZXh0ICs9IGYiXG57c3BlYWtlcn06e3dvcmQudGV4dH0iCgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgogICAgZGVmIF9nZXRfbmV4dF9zZWdtZW50KAogICAgICAgIHNlbGYsCiAgICAgICAgd29yZDogX1dvcmRUaW1lc3RhbXAsCiAgICApOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgbmV4dCBkaWFyaXphdGlvbiBzZWdtZW50IHRoZSBnaXZlbiB3b3JkIGZhbGxzIGludG8uIFRoZSBgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXhgIHdpbGwgYmUgdXBkYXRlZAogICAgICAgIGFjY29yZGluZ2x5LgoKICAgICAgICA6cGFyYW0gd29yZDogVGhlIHdvcmQgdGltZXN0YW1wIHRvIG1hdGNoIHRvIHRoZSBuZXh0IHNlZ21lbnQuCiAgICAgICAgIiIiCiAgICAgICAgIyBJZiB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudCBpcyB0aGUgbGFzdCBzZWdtZW50LCByZXR1cm4gaXQ6CiAgICAgICAgaWYgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPT0gbGVuKHNlbGYuX3NlZ21lbnRzKSAtIDE6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIEdldCB0aGUgbGFzdCBjaG9zZW4gZGlhcml6YXRpb24gc2VnbWVudDoKICAgICAgICBsYXN0X2Nob3NlbiA9IHNlbGYuX3NlZ21lbnRzW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQoKICAgICAgICAjIE5vbmUgdmFsdWUgbWF5IGFwcGVhciBpZiB0aGUgd29yZCBpcyB0aGUgbGFzdCB3b3JkIGluIHRoZSBhdWRpbyBmaWxlLCBvciBpdCB3YXMgc3BsaXQgZHVyaW5nIGluZmVyZW5jZS4gSW4KICAgICAgICAjIHRoYXQgY2FzZSwgd2UnbGwgc2V0IHRoZSBsYXN0IHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBsZW4oc2VsZi5fc2VnbWVudHMpIC0gMQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGUgbGFzdCBjaG9zZW4gc2VnbWVudDoKICAgICAgICBpZiB3b3JkLmVuZCA8PSBsYXN0X2Nob3Nlbi5zdGFydDoKICAgICAgICAgICAgIyBUaGVuIGl0IGlzIHN0aWxsIHRoZSBjbG9zZXN0IHNlZ21lbnQKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICMgV2UgY2hlY2sgaWYgaXQgZW5kcyBpbnNpZGUgdGhlIGxhc3QgY2hvc2VuIHNlZ21lbnQ6CiAgICAgICAgaWYgd29yZC5lbmQgPCBsYXN0X2Nob3Nlbi5lbmQ6CiAgICAgICAgICAgICMgVGhlbiBpdCBzdGlsbCBpcyB0aGUgY2xvc2VzdCBzZWdtZW50CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICAjIFRoZSB3b3JkIGVuZHMgYWZ0ZXIgdGhlIHNlZ21lbnQsIHdlIG5lZWQgdG8gY29sbGVjdCBhbGwgbmV4dCBzZWdtZW50cyB1cCB1bnRpbCB0aGUgd29yZCBlbmRzIGJlZm9yZSB0aGVtOgogICAgICAgIHBvc3NpYmxlX3NlZ21lbnRzID0gW3NlbGYuX2xhc3RfY2hvc2VuX2luZGV4XQogICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYuX2xhc3RfY2hvc2VuX2luZGV4ICsgMSwgbGVuKHNlbGYuX3NlZ21lbnRzKSk6CiAgICAgICAgICAgIGlmIHdvcmQuZW5kID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kOgogICAgICAgICAgICAgICAgcG9zc2libGVfc2VnbWVudHMuYXBwZW5kKGkpCiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwb3NzaWJsZV9zZWdtZW50cy5hcHBlbmQoaSkKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyBDaGVjayBmb3IgdGhlIG1vc3Qgb3ZlcmxhcHBpbmcgb3B0aW9uOgogICAgICAgIGJlc3Rfb3ZlcmxhcCA9IDAKICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBOb25lCiAgICAgICAgZm9yIGkgaW4gcG9zc2libGVfc2VnbWVudHM6CiAgICAgICAgICAgICMgSWYgdGhlIHdvcmQgc3RhcnRzIGJlZm9yZSBzZWdtZW50OgogICAgICAgICAgICBpZiB3b3JkLnN0YXJ0IDw9IHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0OgogICAgICAgICAgICAgICAgIyBJZiBpdCBlbmRzIGJlZm9yZSB0aGUgc2VnbWVudCwgdGhlcmUgaXMgYW4gb3ZlcmxhcCBmcm9tIHRoZSBzdGFydCBvZiB0aGUgc2VnbWVudCB0byB0aGUgZW5kIG9mIHRoZQogICAgICAgICAgICAgICAgIyB3b3JkOgogICAgICAgICAgICAgICAgaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gc2VsZi5fc2VnbWVudHNbaV0uc3RhcnQKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgIyBUaGUgd29yZCBpcyB3cmFwcGluZyB0aGUgc2VnbWVudCwgdGhlIG92ZXJsYXAgaXMgdGhlIHNlZ21lbnQncyBsZW5ndGg6CiAgICAgICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHNlbGYuX3NlZ21lbnRzW2ldLmVuZCAtIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0CiAgICAgICAgICAgICMgVGhlIHdvcmQgc3RhcnRzIGluIHNlZ21lbnQsIGNoZWNrIGlmIHRoZSB3b3JkIGVuZHMgaW4gaXQ6CiAgICAgICAgICAgIGVsaWYgd29yZC5lbmQgPCBzZWxmLl9zZWdtZW50c1tpXS5lbmQ6CiAgICAgICAgICAgICAgICAjIFRoZSBvdmVybGFwIGlzIHRoZSB3b3JkJ3MgbGVuZ3RoOgogICAgICAgICAgICAgICAgb3ZlcmxhcCA9IHdvcmQuZW5kIC0gd29yZC5zdGFydAogICAgICAgICAgICAjIFRoZSB3b3JkIHN0YXJ0IGluIHNlZ21lbnQgYnV0IGVuZHMgYWZ0ZXIgaXQsIHRoZSBvdmVybGFwIGlzIGZyb20gdGhlIHdvcmQncyBzdGFydCB0byB0aGUgc2VnbWVudCdzIGVuZDoKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIG92ZXJsYXAgPSBzZWxmLl9zZWdtZW50c1tpXS5lbmQgLSB3b3JkLnN0YXJ0CiAgICAgICAgICAgICMgQ2hlY2sgZm9yIG5ldyBiZXN0IG92ZXJsYXA6CiAgICAgICAgICAgIGlmIG92ZXJsYXAgPiBiZXN0X292ZXJsYXA6CiAgICAgICAgICAgICAgICBiZXN0X292ZXJsYXAgPSBvdmVybGFwCiAgICAgICAgICAgICAgICBtb3N0X292ZXJsYXBwaW5nX3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgaWYgbW9zdF9vdmVybGFwcGluZ19zZWdtZW50X2luZGV4IGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLl9sYXN0X2Nob3Nlbl9pbmRleCA9IG1vc3Rfb3ZlcmxhcHBpbmdfc2VnbWVudF9pbmRleAogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgIyBJZiB0aGVyZSBpcyBubyBvdmVybGFwcGluZyBzZWdtZW50LCByZXR1cm4gdGhlIGNsb3Nlc3Qgc2VnbWVudDoKICAgICAgICBiZXN0X2Rpc3RhbmNlID0gTm9uZQogICAgICAgIGNsb3Nlc3Rfc2VnbWVudF9pbmRleCA9IE5vbmUKICAgICAgICBmb3IgaSBpbiBwb3NzaWJsZV9zZWdtZW50czoKICAgICAgICAgICAgZGlzdGFuY2UgPSAoCiAgICAgICAgICAgICAgICB3b3JkLnN0YXJ0IC0gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBpZiB3b3JkLnN0YXJ0ID4gc2VsZi5fc2VnbWVudHNbaV0uZW5kCiAgICAgICAgICAgICAgICBlbHNlIHNlbGYuX3NlZ21lbnRzW2ldLnN0YXJ0IC0gd29yZC5lbmQKICAgICAgICAgICAgKQogICAgICAgICAgICBpZiBiZXN0X2Rpc3RhbmNlIGlzIE5vbmUgb3IgZGlzdGFuY2UgPCBiZXN0X2Rpc3RhbmNlOgogICAgICAgICAgICAgICAgYmVzdF9kaXN0YW5jZSA9IGRpc3RhbmNlCiAgICAgICAgICAgICAgICBjbG9zZXN0X3NlZ21lbnRfaW5kZXggPSBpCiAgICAgICAgc2VsZi5fbGFzdF9jaG9zZW5faW5kZXggPSBjbG9zZXN0X3NlZ21lbnRfaW5kZXgKCgpjbGFzcyBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrKEJhc2VUYXNrKToKICAgICIiIgogICAgQSB0YXNrIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIGZpbGUgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLgogICAgIiIiCgogICAgY2xhc3MgX1dvcmRUaW1lc3RhbXAoTmFtZWRUdXBsZSk6CiAgICAgICAgIiIiCiAgICAgICAgQSB3b3JkIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgdGltZXN0YW1wcyBhbmQgc3BlYWtlciBsYWJlbCAoY2hhbm5lbCB0aGUgd29yZCB3YXMgdGFrZW4gZnJvbSkuCiAgICAgICAgIiIiCgogICAgICAgIHN0YXJ0OiBmbG9hdAogICAgICAgIGVuZDogZmxvYXQKICAgICAgICBzcGVha2VyOiBzdHIKICAgICAgICB0ZXh0OiBzdHIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgYXVkaW9fZmlsZTogUGF0aCwgdGV4dF9maWxlOiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSB0YXNrLgoKICAgICAgICA6cGFyYW0gYXVkaW9fZmlsZTogUGF0aCB0byB0aGUgYXVkaW8gZmlsZSB0aGF0IHdhcyB0cmFuc2NyaWJlZC4KICAgICAgICA6cGFyYW0gdGV4dF9maWxlOiAgUGF0aCB0byB0aGUgdGV4dCBmaWxlIHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvLgogICAgICAgICIiIgogICAgICAgIHN1cGVyKCkuX19pbml0X18oCiAgICAgICAgICAgIGF1ZGlvX2ZpbGU9YXVkaW9fZmlsZSwgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9e30sIHRleHRfZmlsZT10ZXh0X2ZpbGUKICAgICAgICApCiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHM6IExpc3RbVHVwbGVbc3RyLCBkaWN0XV0gPSBbXQoKICAgIEBwcm9wZXJ0eQogICAgZGVmIHRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKHNlbGYpIC0+IExpc3RbVHVwbGVbc3RyLCBkaWN0XV06CiAgICAgICAgIiIiCiAgICAgICAgR2V0IHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCBjaGFubmVscy4KICAgICAgICAiIiIKICAgICAgICByZXR1cm4gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKCiAgICBkZWYgZG9fdGFzayhzZWxmKToKICAgICAgICAiIiIKICAgICAgICBUcnkgdG8gcGVyZm9ybSB0aGUgdGFzayBzdG9yaW5nIGFuIGVycm9yIGlmIG9jY3VycmVkLgogICAgICAgICIiIgogICAgICAgIGZvciBfLCBjaGFubmVsX291dHB1dCBpbiBzZWxmLl90cmFuc2NyaXB0aW9uX291dHB1dF9jaGFubmVsczoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShjaGFubmVsX291dHB1dCwgc3RyKToKICAgICAgICAgICAgICAgIHNlbGYuX2Vycm9yID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMKICAgICAgICAgICAgICAgIHJldHVybgogICAgICAgIHN1cGVyKCkuZG9fdGFzaygpCgogICAgZGVmIHRvX3R1cGxlKHNlbGYpIC0+IFR1cGxlW3N0ciwgZGljdF06CiAgICAgICAgIiIiCiAgICAgICAgQ29udmVydCB0aGUgdGFzayB0byBhIHR1cGxlIHRvIHJlY29uc3RydWN0IGl0IGxhdGVyICh1c2VkIGZvciBtdWx0aXByb2Nlc3NpbmcgdG8gcGFzcyBpbiBxdWV1ZSkuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY29udmVydGVkIHRhc2suCiAgICAgICAgIiIiCiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSBzdXBlcigpLnRvX3R1cGxlKCkKICAgICAgICB0YXNrX2t3YXJncy5wb3AoInRyYW5zY3JpcHRpb25fb3V0cHV0IikKICAgICAgICByZXR1cm4gdGFza19jbGFzcywgdGFza19rd2FyZ3MKCiAgICBkZWYgX2RvX3Rhc2soc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgUGVyZm9ybSB0aGUgdGFzayAtIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9uIHRvIHRoZSBzdG9yZWQgZmlsZSBwYXRoIHdpdGggcmVzcGVjdCB0byB0aGUgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uCiAgICAgICAgcGVyIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgIyBDYXN0IHRoZSBjaHVua3MgdG8gd29yZCB0aW1lc3RhbXBzIHR1cGxlczoKICAgICAgICB3b3Jkc19wZXJfY2hhbm5lbCA9IFsKICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fV29yZFRpbWVzdGFtcCgKICAgICAgICAgICAgICAgICAgICBzdGFydD1jaHVua1sidGltZXN0YW1wIl1bMF0sCiAgICAgICAgICAgICAgICAgICAgZW5kPWNodW5rWyJ0aW1lc3RhbXAiXVsxXSwKICAgICAgICAgICAgICAgICAgICBzcGVha2VyPXNwZWFrZXIsCiAgICAgICAgICAgICAgICAgICAgdGV4dD1jaHVua1sidGV4dCJdLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZm9yIGNodW5rIGluIG91dHB1dFsiY2h1bmtzIl0KICAgICAgICAgICAgXQogICAgICAgICAgICBmb3Igc3BlYWtlciwgb3V0cHV0IGluIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzCiAgICAgICAgXQoKICAgICAgICAjIE1lcmdlIGFuZCBzb3J0IHRoZSB3b3JkcyBwZXIgY2hhbm5lbCBieSB0aGVpciBzdGFydCB0aW1lOgogICAgICAgIHdvcmRzID0gb3BlcmF0b3IuYWRkKCp3b3Jkc19wZXJfY2hhbm5lbCkKICAgICAgICB3b3Jkcy5zb3J0KCkKCiAgICAgICAgIyBXcml0ZSB0aGUgdHJhbnNjcmlwdGlvbiB0byBmaWxlOgogICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmRzWzBdLnNwZWFrZXIKICAgICAgICB0ZXh0ID0gZiJ7Y3VycmVudF9zcGVha2VyfToiCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlIHdvcmQncyBzcGVha2VyIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBjdXJyZW50IG9uZToKICAgICAgICAgICAgaWYgd29yZC5zcGVha2VyICE9IGN1cnJlbnRfc3BlYWtlcjoKICAgICAgICAgICAgICAgICMgQXBwZW5kIGEgbmV3bGluZSBhbmQgdXBkYXRlIHRoZSBuZXcgc3BlYWtlcjoKICAgICAgICAgICAgICAgIGN1cnJlbnRfc3BlYWtlciA9IHdvcmQuc3BlYWtlcgogICAgICAgICAgICAgICAgdGV4dCArPSBmIlxue2N1cnJlbnRfc3BlYWtlcn06IgogICAgICAgICAgICAjIENvbGxlY3QgdGhlIHdvcmQ6CiAgICAgICAgICAgIHRleHQgKz0gd29yZC50ZXh0CgogICAgICAgICMgVXBkYXRlIHRoZSB0cmFuc2NyaXB0aW9uIG91dHB1dCB3aXRoIHRoZSBuZXcgdGV4dCB0byB3cml0ZSBpdCB0byBmaWxlOgogICAgICAgIHNlbGYuX3RyYW5zY3JpcHRpb25fb3V0cHV0WyJ0ZXh0Il0gPSB0ZXh0CiAgICAgICAgc3VwZXIoKS5fZG9fdGFzaygpCgoKY2xhc3MgQmF0Y2hQcm9jZXNzb3I6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucy4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUKICAgIHdvcmtpbmcgYWxvbmcgdGhlIHRyYW5zY3JpYmVyLiBJdCBjYW4gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZQogICAgYXNzb2NpYXRlZCBtZXRob2RzLgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLCBvdXRwdXRfZGlyZWN0b3J5OiBQYXRoKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICBUaGUgbGlzdCBvZiBhbGwgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgICAgICA6cGFyYW0gb3V0cHV0X2RpcmVjdG9yeTogVGhlIG91dHB1dCBkaXJlY3RvcnkgdG8gd3JpdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvLgogICAgICAgICIiIgogICAgICAgICMgU3RvcmUgdGhlIHBhcmFtZXRlcnM6CiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwogICAgICAgIHNlbGYuX291dHB1dF9kaXJlY3RvcnkgPSBvdXRwdXRfZGlyZWN0b3J5CgogICAgICAgICMgUHJlcGFyZSB0aGUgYmF0Y2hpbmcgdmFyaWFibGVzOgogICAgICAgIHNlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA9IDAKICAgICAgICBzZWxmLl90YXNrczogTGlzdFtCYXNlVGFza10gPSBbXQogICAgICAgIHNlbGYuX3Jlc3VsdHM6IExpc3RbVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXV0gPSBbXQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W1VuaW9uW2RpY3QsIHN0cl1dKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBCYXNlVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPWZpbGUsCiAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbl9vdXRwdXQ9YmF0Y2hbaV0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkgLyBmIntmaWxlLnN0ZW19LnR4dCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCiAgICBkZWYgZ2V0X3Rhc2tzKHNlbGYpIC0+IExpc3RbQmFzZVRhc2tdOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgdGFza3MgdG8gcGVyZm9ybS4KCiAgICAgICAgOnJldHVybnM6IFRoZSB0YXNrcyB0byBwZXJmb3JtLgogICAgICAgICIiIgogICAgICAgIHRhc2tzID0gc2VsZi5fdGFza3MKICAgICAgICBzZWxmLl90YXNrcyA9IFtdCiAgICAgICAgcmV0dXJuIHRhc2tzCgogICAgZGVmIGRvX3Rhc2tzKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIFBlcmZvcm0gdGhlIHRhc2tzLiBTaG91bGQgYmUgdXNlZCBpZiBubyBtdWx0aXByb2Nlc3NpbmcgcXVldWUgaXMgZ2l2ZW4gdG8gYSB0cmFuc2NyaWJlci4KICAgICAgICAiIiIKICAgICAgICBmb3IgdGFzayBpbiBzZWxmLmdldF90YXNrcygpOgogICAgICAgICAgICB0YXNrLmRvX3Rhc2soKQogICAgICAgICAgICBzZWxmLl9yZXN1bHRzLmFwcGVuZCgodGFzay5pc19mYWlsZWQoKSwgdGFzay5nZXRfcmVzdWx0KCkpKQoKICAgIGRlZiBnZXRfcmVzdWx0cyhzZWxmKSAtPiBMaXN0W1R1cGxlW2Jvb2wsIFR1cGxlW3N0ciwgc3RyXV1dOgogICAgICAgICIiIgogICAgICAgIEdldCB0aGUgcmVzdWx0cyBvZiB0aGUgdGFza3MuIFRoZSBzdG9yZWQgcmVzdWx0cyBhcmUgdGhlbiBjbGVhcmVkLgoKICAgICAgICA6cmV0dXJuczogVGhlIHJlc3VsdHMgb2YgdGhlIHRhc2tzLgogICAgICAgICIiIgogICAgICAgIHJlc3VsdHMgPSBzZWxmLl9yZXN1bHRzCiAgICAgICAgc2VsZi5fcmVzdWx0cyA9IFtdCiAgICAgICAgcmV0dXJuIHJlc3VsdHMKCiAgICBkZWYgX2dldF9jdXJyZW50X2ZpbGVzKHNlbGYsIGJhdGNoX3NpemU6IGludCkgLT4gTGlzdFtQYXRoXToKICAgICAgICAiIiIKICAgICAgICBHZXQgdGhlIGN1cnJlbnQgZmlsZXMgdG8gcHJvY2Vzcy4KCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6IFRoZSBiYXRjaCBzaXplIHRvIHByb2dyZXNzIHRoZSBjdXJyZW50IGZpbGUgaW5kZXguCgogICAgICAgIDpyZXR1cm5zOiBUaGUgY3VycmVudCBmaWxlcyB0byBwcm9jZXNzLgogICAgICAgICIiIgogICAgICAgIGVuZF9pbmRleCA9ICgKICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICsgYmF0Y2hfc2l6ZQogICAgICAgICAgICBpZiBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggKyBiYXRjaF9zaXplIDwgbGVuKHNlbGYuX2F1ZGlvX2ZpbGVzKQogICAgICAgICAgICBlbHNlIGxlbihzZWxmLl9hdWRpb19maWxlcykKICAgICAgICApCiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleCA6IGVuZF9pbmRleF0KICAgICAgICBzZWxmLl9jdXJyZW50X2ZpbGVfaW5kZXggPSBlbmRfaW5kZXgKICAgICAgICByZXR1cm4gY3VycmVudF9maWxlcwoKCmNsYXNzIFNwZWVjaERpYXJpemF0aW9uQmF0Y2hQcm9jZXNzb3IoQmF0Y2hQcm9jZXNzb3IpOgogICAgIiIiCiAgICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIGJhdGNoZXMgb2YgdHJhbnNjcmlwdGlvbnMgd2l0aCByZXNwZWN0IHRvIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uLiBUaGUgYmF0Y2gKICAgIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyBhbmQgaXMgYWltZWQgdG8gYmUgd29ya2luZyBhbG9uZyB0aGUgdHJhbnNjcmliZXIuIEl0IGNhbiBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nCiAgICBxdWV1ZSBvciBydW4gdGhlIHRhc2tzIGRpcmVjdGx5IHVzaW5nIHRoZSBhc3NvY2lhdGVkIG1ldGhvZHMuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwgYXVkaW9fZmlsZXM6IExpc3RbUGF0aF0sIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsIHNwZWVjaF9kaWFyaXphdGlvbjogZGljdAogICAgKToKICAgICAgICAiIiIKICAgICAgICBJbml0aWFsaXplIHRoZSBiYXRjaCBwcm9jZXNzb3IuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIFRoZSBvdXRwdXQgZGlyZWN0b3J5IHRvIHdyaXRlIHRoZSB0cmFuc2NyaXB0aW9ucyB0by4KICAgICAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiBBIHNwZWVjaCBkaWFyaXphdGlvbiBkaWN0aW9uYXJ5IHRvIHBhc3MgYWxvbmcgd2l0aCBlYWNoIHByb2Nlc3NlZCBiYXRjaC4KICAgICAgICAiIiIKICAgICAgICBzdXBlcigpLl9faW5pdF9fKGF1ZGlvX2ZpbGVzPWF1ZGlvX2ZpbGVzLCBvdXRwdXRfZGlyZWN0b3J5PW91dHB1dF9kaXJlY3RvcnkpCiAgICAgICAgc2VsZi5fc3BlZWNoX2RpYXJpemF0aW9uID0gc3BlZWNoX2RpYXJpemF0aW9uCiAgICAgICAgc2VsZi5fYXVkaW9fZmlsZXMgPSBhdWRpb19maWxlcwoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdldCB0aGUgcmVsZXZhbnQgZmlsZXMgYmVsb25ncyB0byB0aGUgZ2l2ZW4gYmF0Y2g6CiAgICAgICAgY3VycmVudF9maWxlcyA9IHNlbGYuX2dldF9jdXJyZW50X2ZpbGVzKGJhdGNoX3NpemU9bGVuKGJhdGNoKSkKCiAgICAgICAgIyBCdWlsZCB0aGUgZGlhcml6YXRpb24gdGFza3M6CiAgICAgICAgc2VsZi5fdGFza3MuZXh0ZW5kKAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBTcGVlY2hEaWFyaXphdGlvblRhc2soCiAgICAgICAgICAgICAgICAgICAgYXVkaW9fZmlsZT1maWxlLAogICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRpb25fb3V0cHV0PWJhdGNoW2ldLAogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZT1zZWxmLl9vdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZS5zdGVtfS50eHQiLAogICAgICAgICAgICAgICAgICAgIHNwZWVjaF9kaWFyaXphdGlvbj1zZWxmLl9zcGVlY2hfZGlhcml6YXRpb24uZ2V0KGZpbGUubmFtZSksCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBmb3IgaSwgZmlsZSBpbiBlbnVtZXJhdGUoY3VycmVudF9maWxlcykKICAgICAgICAgICAgXQogICAgICAgICkKCgpjbGFzcyBQZXJDaGFubmVsU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcihCYXRjaFByb2Nlc3Nvcik6CiAgICAiIiIKICAgIEEgYmF0Y2ggcHJvY2Vzc29yIHRvIHByb2Nlc3MgYmF0Y2hlcyBvZiB0cmFuc2NyaXB0aW9ucyBwZXIgY2hhbm5lbC4gVGhlIGJhdGNoIHByb2Nlc3NvciBpcyBjcmVhdGluZyB0YXNrcyB3aXRoIHRoZQogICAgc2VsZWN0ZWQgYW1vdW50IG9mIGNoYW5uZWxzIGdpdmVuIGFuZCBpcyBhaW1lZCB0byBiZSB3b3JraW5nIGFsb25nIHRoZSB0cmFuc2NyaWJlci4gSXQgY2FuIGJlIHVzZWQgd2l0aAogICAgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIG9yIHJ1biB0aGUgdGFza3MgZGlyZWN0bHkgdXNpbmcgdGhlIGFzc29jaWF0ZWQgbWV0aG9kcy4KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXygKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIG91dHB1dF9kaXJlY3Rvcnk6IFBhdGgsCiAgICAgICAgbl9jaGFubmVsczogaW50LAogICAgICAgIHNwZWFrZXJzOiBMaXN0W3N0cl0sCiAgICApOgogICAgICAgICIiIgogICAgICAgIEluaXRpYWxpemUgdGhlIGJhdGNoIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGF1ZGlvX2ZpbGVzOiAgICAgIFRoZSBsaXN0IG9mIGFsbCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiBUaGUgb3V0cHV0IGRpcmVjdG9yeSB0byB3cml0ZSB0aGUgdHJhbnNjcmlwdGlvbnMgdG8uCiAgICAgICAgOnBhcmFtIG5fY2hhbm5lbHM6ICAgICAgIFRoZSBudW1iZXIgb2YgY2hhbm5lbHMgaW4gZWFjaCBhdWRpbyBmaWxlIHRvIHRyYW5zY3JpYmUuCiAgICAgICAgOnBhcmFtIHNwZWFrZXJzOiAgICAgICAgIFRoZSBzcGVha2VycyBsYWJlbHMgdG8gdXNlIGZvciBlYWNoIGNoYW5uZWwuCiAgICAgICAgIiIiCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhhdWRpb19maWxlcz1hdWRpb19maWxlcywgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5KQoKICAgICAgICAjIFN0b3JlIHRoZSBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX25fY2hhbm5lbHMgPSBuX2NoYW5uZWxzCiAgICAgICAgc2VsZi5fc3BlYWtlcnMgPSBzcGVha2VycwoKICAgICAgICAjIFByZXBhcmUgYSBjaGFubmVsIGJ1ZmZlciB0byBzdG9yZSB0aGUgY2hhbm5lbHMgdW50aWwgdGhlIGN1cnJlbnQgdGFzayBjcmVhdGVkIGlzIGZ1bGx5IGNvdmVyZWQ6CiAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzOiBTcGVlY2hEaWFyaXphdGlvblBlckNoYW5uZWxUYXNrID0gTm9uZQoKICAgIGRlZiBwcm9jZXNzX2JhdGNoKHNlbGYsIGJhdGNoOiBMaXN0W2RpY3RdKToKICAgICAgICAiIiIKICAgICAgICBQcm9jZXNzIGEgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMuIFRhc2tzIHJlbGF0ZWQgdG8gdGhlIGdpdmVuIGJhdGNoIHdpbGwgYmUgY3JlYXRlZCBhbmQgc3RvcmVkIGluIHRoZSBiYXRjaAogICAgICAgIHByb2Nlc3Nvci4KCiAgICAgICAgOnBhcmFtIGJhdGNoOiBUaGUgYmF0Y2ggb2YgdHJhbnNjcmlwdGlvbnMgdG8gcHJvY2Vzcy4KICAgICAgICAiIiIKICAgICAgICAjIEdvIG92ZXIgdGhlIGJhdGNoIGFuZCBjcmVhdGUgdGhlIHRhc2tzOgogICAgICAgIGZvciBvdXRwdXQgaW4gYmF0Y2g6CiAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgaXMgYSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgIGlmIG5vdCBzZWxmLl90YXNrX2luX3Byb2Nlc3M6CiAgICAgICAgICAgICAgICAjIENyZWF0ZSBhIG5ldyB0YXNrOgogICAgICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzID0gU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaygKICAgICAgICAgICAgICAgICAgICBhdWRpb19maWxlPXNlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0sCiAgICAgICAgICAgICAgICAgICAgdGV4dF9maWxlPXNlbGYuX291dHB1dF9kaXJlY3RvcnkKICAgICAgICAgICAgICAgICAgICAvIGYie3NlbGYuX2F1ZGlvX2ZpbGVzW3NlbGYuX2N1cnJlbnRfZmlsZV9pbmRleF0uc3RlbX0udHh0IiwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBHZXQgdGhlIGNoYW5uZWwncyBzcGVha2VyOgogICAgICAgICAgICBzcGVha2VyID0gc2VsZi5fc3BlYWtlcnNbCiAgICAgICAgICAgICAgICBsZW4oc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzKQogICAgICAgICAgICBdCiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgY2hhbm5lbCBpbnRvIHRoZSBwcm9jZXNzZWQgdGFzazoKICAgICAgICAgICAgc2VsZi5fdGFza19pbl9wcm9jZXNzLnRyYW5zY3JpcHRpb25fb3V0cHV0X2NoYW5uZWxzLmFwcGVuZCgKICAgICAgICAgICAgICAgIChzcGVha2VyLCBvdXRwdXQpCiAgICAgICAgICAgICkKICAgICAgICAgICAgIyBDaGVjayBpZiB0aGUgdGFzayBpcyBmdWxseSBjb3ZlcmVkIChhbGwgY2hhbm5lbHMgYXJlIGNvbGxlY3RlZCk6CiAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgIGxlbihzZWxmLl90YXNrX2luX3Byb2Nlc3MudHJhbnNjcmlwdGlvbl9vdXRwdXRfY2hhbm5lbHMpCiAgICAgICAgICAgICAgICA9PSBzZWxmLl9uX2NoYW5uZWxzCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICAjIENvbGxlY3QgdGhlIHRhc2sgYW5kIHJlc2V0IHRoZSB0YXNrIGluIHByb2Nlc3M6CiAgICAgICAgICAgICAgICBzZWxmLl90YXNrcy5hcHBlbmQoc2VsZi5fdGFza19pbl9wcm9jZXNzKQogICAgICAgICAgICAgICAgc2VsZi5fY3VycmVudF9maWxlX2luZGV4ICs9IDEKICAgICAgICAgICAgICAgIHNlbGYuX3Rhc2tfaW5fcHJvY2VzcyA9IE5vbmUKCgpjbGFzcyBUcmFuc2NyaWJlcjoKICAgICIiIgogICAgQSB0cmFuc2NyaXB0aW9uIHdyYXBwZXIgZm9yIHRoZSBIdWdnaW5nZmFjZSdzIEFTUiBwaXBlbGluZSAtCiAgICBodHRwczovL2h1Z2dpbmdmYWNlLmNvL3RyYW5zZm9ybWVycy9tYWluX2NsYXNzZXMvcGlwZWxpbmVzLmh0bWwjdHJhbnNmb3JtZXJzLkF1dG9tYXRpY1NwZWVjaFJlY29nbml0aW9uUGlwZWxpbmUgdG8KICAgIHVzZSB3aXRoIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIC0gaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9vcGVuYWkuCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oCiAgICAgICAgc2VsZiwKICAgICAgICBtb2RlbF9uYW1lOiBzdHIsCiAgICAgICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgICAgIHVzZV9mbGFzaF9hdHRlbnRpb25fMjogYm9vbCA9IE5vbmUsCiAgICAgICAgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6IGJvb2wgPSBOb25lLAogICAgICAgIGFzc2lzdGFudF9tb2RlbDogc3RyID0gTm9uZSwKICAgICAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgICAgIGNodW5rX2xlbmd0aF9zOiBpbnQgPSAzMCwKICAgICAgICBiYXRjaF9zaXplOiBpbnQgPSAyLAogICAgICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgICAgICB0cmFuc2xhdGVfdG9fZW5nbGlzaDogYm9vbCA9IEZhbHNlLAogICAgICAgIHJldHVybl90aW1lc3RhbXBzOiBVbmlvbltib29sLCBMaXRlcmFsWyJ3b3JkIl1dID0gRmFsc2UsCiAgICAgICAgcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjogaW50ID0gMCwKICAgICk6CiAgICAgICAgIiIiCiAgICAgICAgSW5pdGlhbGl6ZSB0aGUgdHJhbnNjcmliZXIuCgogICAgICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICBUaGUgbW9kZWwgbmFtZSB0byB1c2UuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gdGhlIE9wZW5BSSdzIFdoaXNwZXIgbW9kZWxzIGZvcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXN0IHJlc3VsdHMgKGZvciBleGFtcGxlICJ0aW55IiwgImJhc2UiLCAibGFyZ2UiLCBldGMuKS4KICAgICAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgVGhlIGRldmljZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gSWYgbm90IGdpdmVuLCB3aWxsIHVzZSBHUFUgaWYgYXZhaWxhYmxlLgogICAgICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICBXaGV0aGVyIHRvIHVzZSB0aGUgRmxhc2ggQXR0ZW50aW9uIDIgaW1wbGVtZW50YXRpb24uIEl0IGNhbiBiZSB1c2VkIG9ubHkgd2l0aAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmUgb2YgdGhlIGZvbGxvd2luZyBHUFVzOiBOdmlkaWEgSCBzZXJpZXMgYW5kIE52aWRpYSBBIHNlcmllcy4gVDQgc3VwcG9ydAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWxsIGJlIGF2YWlsYWJsZSBzb29uLgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogSWYgYm90aCBgdXNlX2ZsYXNoX2F0dGVudGlvbl8yYCBhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzYCBhcmUgYE5vbmVgLCB0aGUgb3B0aW1pemF0aW9uIHdpbGwgYmUgY2hvc2VuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9tYXRpY2FsbHkgYWNjb3JkaW5nIHRvIHRoZSBhdmFpbGFibGUgcmVzb3VyY2VzLgoKICAgICAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgV2hldGhlciB0byB1c2UgdGhlIEJldHRlciBUcmFuc2Zvcm1lcnMgbGlicmFyeSB0byBmdXJ0aGVyIG9wdGltaXplIHRoZSBtb2RlbC4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvdWxkIGJlIHVzZWQgZm9yIGFsbCB1c2UgY2FzZXMgdGhhdCBkbyBub3Qgc3VwcG9ydCBmbGFzaCBhdHRlbnRpb24gMi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZhaWxhYmxlIHJlc291cmNlcy4KICAgICAgIDpwYXJhbSBhc3Npc3RhbnRfbW9kZWw6ICAgICAgICAgICBUaGUgYXNzaXN0YW50IG1vZGVsIG5hbWUgdG8gdXNlIGZvciBpbmZlcmVuY2UuIE5vdGljZSB0aGF0IHRoZSBvcHRpbWl6YXRpb25zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChmbGFzaCBhdHRlbnRpb24gMiBhbmQgYmV0dGVyIHRyYW5zZm9ybWVycykgd2lsbCBiZSBhcHBsaWVkIGZvciB0aGUgYXNzaXN0YW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzIHdlbGwuIFNob3VsZCBiZSBhIG1vZGVsIGZyb20gSHVnZ2luZ2ZhY2UncyBkaXN0aWwtd2hpc3BlciAoc2VlIGhlcmUgZm9yCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vcmUgaW5mb3JtYXRpb246IGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9kaXN0aWwtd2hpc3BlcikuCiAgICAgICAgOnBhcmFtIG1heF9uZXdfdG9rZW5zOiAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICAgICAgOnBhcmFtIGNodW5rX2xlbmd0aF9zOiAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICAgICAgOnBhcmFtIGJhdGNoX3NpemU6ICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICAgICAgOnBhcmFtIHNwb2tlbl9sYW5ndWFnZTogICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdCBpdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZWFjaCBjaHVuay4KICAgICAgICA6cGFyYW0gdHJhbnNsYXRlX3RvX2VuZ2xpc2g6ICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guIERlZmF1bHQgaXMgRmFsc2UuCiAgICAgICAgOnBhcmFtIHJldHVybl90aW1lc3RhbXBzOiAgICAgICAgIFdoZXRoZXIgdG8gcmV0dXJuIHRoZSB0aW1lc3RhbXBzIG9mIHRoZSB3b3Jkcy4gSWYgIndvcmQiLCB3aWxsIHJldHVybiB0aGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wcyBvZiBlYWNoIHdvcmQuIElmIFRydWUgd2lsbCByZXR1cm4gdGhlIHRpbWVzdGFtcHMgb2YgZWFjaCBjaHVuay4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRGVmYXVsdCBpcyBGYWxzZS4gQWltZWQgdG8gYmUgdXNlZCBmb3Igc3BlZWNoIGRpYXJpemF0aW9uLgogICAgICAgIDpwYXJhbSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uOiBXaGV0aGVyIHRvIGRvIHBlciBjaGFubmVsIHRyYW5zY3JpcHRpb24uIElmIG5lZWRlZCB0byBydW4gcGVyIGNoYW5uZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNjcmlwdGlvbiwgcGFzcyB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGV4cGVjdGVkIGZvciBlYWNoIGF1ZGlvIGZpbGUgaGVyZS4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCBtZWFucyByZWd1bGFyIHRyYW5zY3JpcHRpb24gKG1lcmdlIGNoYW5uZWxzKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uYCBpcyBub3QgMCwgYGJhdGNoX3NpemVgIG11c3QgYmUgdHJlYXRlZCB0bwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZSB0aGUgbnVtYmVyIG9mIGNoYW5uZWxzIGFuZCBub3QgYXVkaW8gZmlsZXMuIEFpbWVkIHRvIGJlIHVzZWQgZm9yIHBlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFubmVsIHNwZWVjaCBkaWFyaXphdGlvbi4KICAgICAgICAiIiIKICAgICAgICAjIFN0b3JlIGxvYWRpbmcgcGFyYW1ldGVyczoKICAgICAgICBzZWxmLl9tb2RlbF9uYW1lID0gbW9kZWxfbmFtZQogICAgICAgIHNlbGYuX2RldmljZSA9IGRldmljZQogICAgICAgIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMiA9IHVzZV9mbGFzaF9hdHRlbnRpb25fMgogICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMKICAgICAgICBzZWxmLl9tYXhfbmV3X3Rva2VucyA9IG1heF9uZXdfdG9rZW5zCiAgICAgICAgc2VsZi5fY2h1bmtfbGVuZ3RoX3MgPSBjaHVua19sZW5ndGhfcwogICAgICAgIHNlbGYuX2JhdGNoX3NpemUgPSBiYXRjaF9zaXplCiAgICAgICAgc2VsZi5fcmV0dXJuX3RpbWVzdGFtcHMgPSByZXR1cm5fdGltZXN0YW1wcwogICAgICAgIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gPSBwZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uCgogICAgICAgICMgU3RvcmUgZ2VuZXJhdGlvbiBwYXJhbWV0ZXJzOgogICAgICAgIHNlbGYuX2Fzc2lzdGFudF9tb2RlbCA9IGFzc2lzdGFudF9tb2RlbAogICAgICAgIHNlbGYuX3Nwb2tlbl9sYW5ndWFnZSA9IHNwb2tlbl9sYW5ndWFnZQogICAgICAgIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoID0gdHJhbnNsYXRlX3RvX2VuZ2xpc2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSB0cmFuc2NyaXB0aW9uIG9iamVjdHM6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZTogQXV0b21hdGljU3BlZWNoUmVjb2duaXRpb25QaXBlbGluZSA9IE5vbmUKICAgICAgICBzZWxmLl9nZW5lcmF0ZV9rd2FyZ3M6IGRpY3QgPSBOb25lCgogICAgZGVmIGxvYWQoc2VsZik6CiAgICAgICAgIiIiCiAgICAgICAgTG9hZCB0aGUgdHJhbnNjcmliZXIuIE11c3QgYmUgY2FsbGVkIGJlZm9yZSB0cmFuc2NyaWJpbmcuCiAgICAgICAgIiIiCiAgICAgICAgIyBTZXQgdGhlIGRldmljZSBhbmQgZGF0YSB0eXBlIHRvIHVzZSAocHJlZmVyIEdQVSBpZiBhdmFpbGFibGUpOgogICAgICAgIGRldmljZSA9IHRvcmNoLmRldmljZSgKICAgICAgICAgICAgc2VsZi5fZGV2aWNlIG9yICJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIKICAgICAgICApCiAgICAgICAgdG9yY2hfZHR5cGUgPSB0b3JjaC5mbG9hdDE2IGlmIGRldmljZS50eXBlID09ICJjdWRhIiBlbHNlIHRvcmNoLmZsb2F0MzIKCiAgICAgICAgIyBDaG9vc2UgdGhlIG9wdGltaXphdGlvbiB0byB1c2UgKGluIGNhc2UgdGhlIHVzZXIgZGlkIG5vdCBzcGVjaWZ5IGFueSk6CiAgICAgICAgaWYgKAogICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgaXMgTm9uZQogICAgICAgICAgICBhbmQgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgaXMgTm9uZQogICAgICAgICk6CiAgICAgICAgICAgICMgUHJlZmVyIHRvIHVzZSBmbGFzaCBhdHRlbnRpb24gMiBpZiBhdmFpbGFibGUgYW5kIGN1ZGEgZGV2aWNlIGlzIHN1cHBvcnRlZCAoc2VlIEdQVSBuYW1lcyB0byBhcmNoaXRlY3R1cmUKICAgICAgICAgICAgIyBoZXJlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX052aWRpYV9ncmFwaGljc19wcm9jZXNzaW5nX3VuaXRzI1Rlc2xhKToKICAgICAgICAgICAgaWYgZGV2aWNlLnR5cGUgPT0gImN1ZGEiIGFuZCBpc19mbGFzaF9hdHRuXzJfYXZhaWxhYmxlKCk6CiAgICAgICAgICAgICAgICBjdWRhX2RldmljZV9uYW1lID0gdG9yY2guY3VkYS5nZXRfZGV2aWNlX3Byb3BlcnRpZXMoZGV2aWNlKS5uYW1lCiAgICAgICAgICAgICAgICBpZiBhbnkoCiAgICAgICAgICAgICAgICAgICAgY3VkYV9kZXZpY2VfbmFtZS5zdGFydHN3aXRoKGdwdV9uYW1lKQogICAgICAgICAgICAgICAgICAgIGZvciBncHVfbmFtZSBpbiBbCiAgICAgICAgICAgICAgICAgICAgICAgICJOVklESUEgQSIsICAjIEZvciBBbXBlcmUgYXJjaGl0ZWN0dXJlIChlLmcuIEExMCwgQTMwLCBBMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEgiLCAgIyBGb3IgSG9wcGVyIGFyY2hpdGVjdHVyZSAoZS5nLiBIMTAwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIEwiLCAgIyBGb3IgQWRhIExvdmVsYWNlIGFyY2hpdGVjdHVyZSAoZS5nLiBMNCwgTDQwKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCAzMCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggMzAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA0MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNDAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAiTlZJRElBIFJUWCA1MCIsICAjIEZvciBBZGEgTG92ZWxhY2UgYXJjaGl0ZWN0dXJlIChSVFggNTAgc2VyaWVzKQogICAgICAgICAgICAgICAgICAgICAgICAjIFdpbGwgYmUgc3VwcG9ydGVkIHNvb24gYWNjb3JkaW5nIHRvIEZsYXNoQXR0ZW50aW9uIEdpdEh1YiByZXBvOgogICAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS9EYW8tQUlMYWIvZmxhc2gtYXR0ZW50aW9uP3RhYj1yZWFkbWUtb3YtZmlsZSNpbnN0YWxsYXRpb24tYW5kLWZlYXR1cmVzCiAgICAgICAgICAgICAgICAgICAgICAgICMgIk5WSURJQSBUNCIsICAjIEZvciBUdXJpbmcgYXJjaGl0ZWN0dXJlIChvbmx5IFQ0KQogICAgICAgICAgICAgICAgICAgICAgICAjICJOVklESUEgUlRYIDIwIiwgICMgRm9yIFR1cmluZyBhcmNoaXRlY3R1cmUgKFJUWCAyMCBzZXJpZXMpCiAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgKToKICAgICAgICAgICAgICAgICAgICBzZWxmLl91c2VfZmxhc2hfYXR0ZW50aW9uXzIgPSBUcnVlCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHNlbGYuX3VzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzID0gVHJ1ZQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgc2VsZi5fdXNlX2JldHRlcl90cmFuc2Zvcm1lcnMgPSBUcnVlCgogICAgICAgICMgQnVpbGQgdGhlIG9wdGltaXphdGlvbnMga3dhcmdzOgogICAgICAgIG1vZGVsX2t3YXJncyA9IHsKICAgICAgICAgICAgImxvd19jcHVfbWVtX3VzYWdlIjogVHJ1ZSwKICAgICAgICAgICAgInVzZV9zYWZldGVuc29ycyI6IFRydWUsCiAgICAgICAgfQogICAgICAgIGlmIHNlbGYuX3VzZV9mbGFzaF9hdHRlbnRpb25fMjoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgRmxhc2hBdHRlbnRpb24yIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYGZsYXNoLWF0dG5gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBmbGFzaC1hdHRuIC0tbm8tYnVpbGQtaXNvbGF0aW9uYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAiZmxhc2hfYXR0ZW50aW9uXzIiCiAgICAgICAgZWxpZiBzZWxmLl91c2VfYmV0dGVyX3RyYW5zZm9ybWVyczoKICAgICAgICAgICAgaWYgX0xPR0dFUjoKICAgICAgICAgICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgICAgICAgICAiVXNpbmcgQmV0dGVyVHJhbnNmb3JtZXJzIG9wdGltaXphdGlvbiAtIG1ha2Ugc3VyZSB0aGUgYG9wdGltdW1gIHBhY2thZ2UgaXMgaW5zdGFsbGVkIHZpYSAiCiAgICAgICAgICAgICAgICAgICAgImBwaXAgaW5zdGFsbCAtVSBvcHRpbXVtYCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgbW9kZWxfa3dhcmdzWyJhdHRuX2ltcGxlbWVudGF0aW9uIl0gPSAic2RwYSIKCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBzcGVlY2ggcmVjb2duaXRpb24gcGlwZWxpbmU6CiAgICAgICAgc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSA9IHBpcGVsaW5lKAogICAgICAgICAgICB0YXNrPSJhdXRvbWF0aWMtc3BlZWNoLXJlY29nbml0aW9uIiwKICAgICAgICAgICAgbW9kZWw9c2VsZi5fbW9kZWxfbmFtZSwKICAgICAgICAgICAgbW9kZWxfa3dhcmdzPW1vZGVsX2t3YXJncy5jb3B5KCksCiAgICAgICAgICAgIGJhdGNoX3NpemU9c2VsZi5fYmF0Y2hfc2l6ZSwKICAgICAgICAgICAgbWF4X25ld190b2tlbnM9c2VsZi5fbWF4X25ld190b2tlbnMsCiAgICAgICAgICAgIGNodW5rX2xlbmd0aF9zPXNlbGYuX2NodW5rX2xlbmd0aF9zLAogICAgICAgICAgICByZXR1cm5fdGltZXN0YW1wcz1zZWxmLl9yZXR1cm5fdGltZXN0YW1wcywKICAgICAgICAgICAgdG9yY2hfZHR5cGU9dG9yY2hfZHR5cGUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgKQoKICAgICAgICAjIFByZXBhcmUgdGhlIGdlbmVyYXRpb24ga3dhcmdzOgogICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJncyA9IHsKICAgICAgICAgICAgImxhbmd1YWdlIjogc2VsZi5fc3Bva2VuX2xhbmd1YWdlLAogICAgICAgICAgICAidGFzayI6ICJ0cmFuc2xhdGUiIGlmIHNlbGYuX3RyYW5zbGF0ZV90b19lbmdsaXNoIGVsc2UgInRyYW5zY3JpYmUiLAogICAgICAgIH0KCiAgICAgICAgIyBJbml0aWFsaXplIHRoZSBhc3Npc3RhbnQgbW9kZWwgKGlmIG5lZWRlZCk6CiAgICAgICAgaWYgc2VsZi5fYXNzaXN0YW50X21vZGVsOgogICAgICAgICAgICBhc3Npc3RhbnRfbW9kZWwgPSBBdXRvTW9kZWxGb3JDYXVzYWxMTS5mcm9tX3ByZXRyYWluZWQoCiAgICAgICAgICAgICAgICBzZWxmLl9hc3Npc3RhbnRfbW9kZWwsIHRvcmNoX2R0eXBlPXRvcmNoX2R0eXBlLCAqKm1vZGVsX2t3YXJncwogICAgICAgICAgICApCiAgICAgICAgICAgIGFzc2lzdGFudF9tb2RlbC50byhkZXZpY2UpCiAgICAgICAgICAgIHNlbGYuX2dlbmVyYXRlX2t3YXJnc1siYXNzaXN0YW50X21vZGVsIl0gPSBhc3Npc3RhbnRfbW9kZWwKCiAgICBkZWYgdHJhbnNjcmliZSgKICAgICAgICBzZWxmLAogICAgICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IgPSBOb25lLAogICAgICAgIGJhdGNoZXNfcXVldWU6IFF1ZXVlID0gTm9uZSwKICAgICAgICB2ZXJib3NlOiBib29sID0gRmFsc2UsCiAgICApIC0+IFVuaW9uW0xpc3RbTGlzdFtkaWN0XV0sIE5vbmVdOgogICAgICAgICIiIgogICAgICAgIFRyYW5zY3JpYmUgdGhlIGdpdmVuIGF1ZGlvIGZpbGVzLiBUaGUgdHJhbnNjcmlwdGlvbnMgd2lsbCBiZSBzZW50IHRvIGEgcXVldWUgb3IgYSBiYXRjaCBwcm9jZXNzb3IgZm9yIGZ1cnRoZXIKICAgICAgICBwcm9jZXNzaW5nIGxpa2Ugd3JpdGluZyB0byB0ZXh0IGZpbGVzLiBJZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIHRoZSB0cmFuc2NyaXB0aW9ucyBvdXRwdXRzIGZyb20KICAgICAgICB0aGUgcGlwZWxpbmUgd2lsbCBiZSByZXR1cm5lZC4gT3RoZXJ3aXNlLCBgTm9uZWAgaXMgcmV0dXJuZWQuCgogICAgICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IEEgYmF0Y2ggcHJvY2Vzc29yLgogICAgICAgIDpwYXJhbSBiYXRjaGVzX3F1ZXVlOiAgIEEgbXVsdGlwcm9jZXNzaW5nIHF1ZXVlIHRvIHB1dCB0aGUgYmF0Y2hlcyBpbi4KICAgICAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICBXaGV0aGVyIHRvIHNob3cgYSBwcm9ncmVzcyBiYXIuIERlZmF1bHQgaXMgRmFsc2UuCgogICAgICAgIDpyZXR1cm5zOiBUaGUgdHJhbnNjcmlwdGlvbnMgb3V0cHV0cyBmcm9tIHRoZSBwaXBlbGluZSBpZiBubyBxdWV1ZSBvciBiYXRjaCBwcm9jZXNzb3IgaXMgZ2l2ZW4sIG90aGVyd2lzZSwKICAgICAgICAgICAgICAgICAgYE5vbmVgLgogICAgICAgICIiIgogICAgICAgICMgV3JhcCB0aGUgYXVkaW8gZmlsZXMgd2l0aCBhIGZ1bmN0aW9uIHRvIGl0ZXJhdGUgb3ZlciB0aGVtIHZpYSBhIGdlbmVyYXRvciAoc2F2ZSBtZW1vcnkgYW5kIHJ1bnRpbWUgd2l0aAogICAgICAgICMgSHVnZ2luZ2ZhY2UncyBwaXBlbGluZXMgYXMgdGhleSBwcmVsb2FkIGVhY2ggaW5wdXQgd2hpbGUgaW5mZXJlbmNlIGlzIHJ1bm5pbmcpOgogICAgICAgIGRlZiBhdWRpb19pdGVyYXRvcigpIC0+IEdlbmVyYXRvcltVbmlvbltkaWN0LCBzdHJdLCBOb25lLCBOb25lXToKICAgICAgICAgICAgaWYgc2VsZi5fcGVyX2NoYW5uZWxfdHJhbnNjcmlwdGlvbjoKICAgICAgICAgICAgICAgIGZvciBhdWRpb19maWxlIGluIGF1ZGlvX2ZpbGVzOgogICAgICAgICAgICAgICAgICAgIGF1ZGlvLCBzYW1wbGluZ19yYXRlID0gdG9yY2hhdWRpby5sb2FkKHN0cihhdWRpb19maWxlKSkKICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGF1ZGlvLm51bXB5KCkKICAgICAgICAgICAgICAgICAgICBmb3IgY2hhbm5lbCBpbiBhdWRpbzoKICAgICAgICAgICAgICAgICAgICAgICAgeWllbGQgeyJyYXciOiBjaGFubmVsLCAic2FtcGxpbmdfcmF0ZSI6IHNhbXBsaW5nX3JhdGV9CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmb3IgYXVkaW9fZmlsZSBpbiBhdWRpb19maWxlczoKICAgICAgICAgICAgICAgICAgICB5aWVsZCBzdHIoYXVkaW9fZmlsZSkKCiAgICAgICAgIyBDcmVhdGUgYSBiYXRjaCBpdGVyYXRvcjoKICAgICAgICBkZWYgYmF0Y2hfaXRlcmF0b3IoKSAtPiBHZW5lcmF0b3JbTGlzdFtVbmlvbltkaWN0LCBzdHJdXSwgTm9uZSwgTm9uZV06CiAgICAgICAgICAgIGJhdGNoID0gW10KICAgICAgICAgICAgZm9yIGF1ZGlvIGluIGF1ZGlvX2l0ZXJhdG9yKCk6CiAgICAgICAgICAgICAgICBiYXRjaC5hcHBlbmQoYXVkaW8pCiAgICAgICAgICAgICAgICBpZiBsZW4oYmF0Y2gpID09IHNlbGYuX2JhdGNoX3NpemU6CiAgICAgICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IFtdCiAgICAgICAgICAgIGlmIGJhdGNoOgogICAgICAgICAgICAgICAgeWllbGQgYmF0Y2gKCiAgICAgICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgICAgICBvdXRwdXRzID0gW10KCiAgICAgICAgIyBJbmZlciB0aHJvdWdoIHRoZSBwaXBlbGluZToKICAgICAgICBmb3IgaW5wdXRfYmF0Y2ggaW4gdHFkbSgKICAgICAgICAgICAgYmF0Y2hfaXRlcmF0b3IoKSBpZiBzZWxmLl9iYXRjaF9zaXplID4gMSBlbHNlIGF1ZGlvX2l0ZXJhdG9yKCksCiAgICAgICAgICAgIGRlc2M9IlRyYW5zY3JpYmluZyIsCiAgICAgICAgICAgIHVuaXQ9ImNoYW5uZWwiIGlmIHNlbGYuX3Blcl9jaGFubmVsX3RyYW5zY3JpcHRpb24gZWxzZSAiYXVkaW8gZmlsZSIsCiAgICAgICAgICAgIHRvdGFsPSgKICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAobGVuKGF1ZGlvX2ZpbGVzKSAvLyBzZWxmLl9iYXRjaF9zaXplKQogICAgICAgICAgICAgICAgICAgICsgKGxlbihhdWRpb19maWxlcykgJSBzZWxmLl9iYXRjaF9zaXplICE9IDApCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAqIChzZWxmLl9wZXJfY2hhbm5lbF90cmFuc2NyaXB0aW9uIG9yIDEpCiAgICAgICAgICAgICksCiAgICAgICAgICAgIGRpc2FibGU9bm90IHZlcmJvc2UsCiAgICAgICAgKToKICAgICAgICAgICAgIyBJbmZlcjoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc2VsZi5fdHJhbnNjcmlwdGlvbl9waXBlbGluZSgKICAgICAgICAgICAgICAgICAgICBpbnB1dF9iYXRjaCwKICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZV9rd2FyZ3M9c2VsZi5fZ2VuZXJhdGVfa3dhcmdzLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZXhjZXB0aW9uOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gc3RyKGV4Y2VwdGlvbikKICAgICAgICAgICAgICAgICMgQWxpZ24gdG8gYmF0Y2ggc2l6ZToKICAgICAgICAgICAgICAgIG91dHB1dF9iYXRjaCA9ICgKICAgICAgICAgICAgICAgICAgICBbb3V0cHV0X2JhdGNoXSAqIGxlbihpbnB1dF9iYXRjaCkKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKGlucHV0X2JhdGNoLCBsaXN0KQogICAgICAgICAgICAgICAgICAgIGVsc2UgW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgIyBUbyBhbGlnbiB3aXRoIGJhdGNoaW5nLCBpZiBiYXRjaCBzaXplIGlzIDEsIHdyYXAgdGhlIG91dHB1dCB3aXRoIGEgbGlzdDoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRfYmF0Y2gsIGRpY3QpOgogICAgICAgICAgICAgICAgb3V0cHV0X2JhdGNoID0gW291dHB1dF9iYXRjaF0KICAgICAgICAgICAgIyBJZiBhIGJhdGNoIHByb2Nlc3NvciBpcyBnaXZlbiwgcHJvY2VzcyB0aGUgYmF0Y2g6CiAgICAgICAgICAgIGlmIGJhdGNoX3Byb2Nlc3NvcjoKICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpdCBkaXJlY3RseToKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPW91dHB1dF9iYXRjaCkKICAgICAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5kb190YXNrcygpCiAgICAgICAgICAgIGVsaWYgYmF0Y2hlc19xdWV1ZToKICAgICAgICAgICAgICAgICMgT3RoZXJ3aXNlLCBxdWV1ZSB0aGUgYmF0Y2g6CiAgICAgICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChvdXRwdXRfYmF0Y2gpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIE90aGVyd2lzZSwgY29sbGVjdCB0aGUgb3V0cHV0IGFzIGlzIHdpdGhvdXQgcHJvY2Vzc2luZzoKICAgICAgICAgICAgICAgIG91dHB1dHMuYXBwZW5kKG91dHB1dF9iYXRjaCkKCiAgICAgICAgIyBDaGVjayBpZiBnaXZlbiBhIG11bHRpcHJvY2Vzc2luZyBxdWV1ZSBvciBhIGJhdGNoIHByb2Nlc3NvcjoKICAgICAgICBpZiBiYXRjaGVzX3F1ZXVlOgogICAgICAgICAgICBiYXRjaGVzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCiAgICAgICAgcmV0dXJuIG91dHB1dHMgaWYgbm90IGJhdGNoX3Byb2Nlc3NvciBlbHNlIE5vbmUKCgojOiBUaGUgdmFsdWUgdG8gc2VuZCBpbnRvIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXMgdG8gc3RvcCB0aGUgcHJvY2VzczoKX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUksgPSAiU1RPUCIKCgpkZWYgX211bHRpcHJvY2Vzc2luZ19wcm9jZXNzX2JhdGNoZXMoCiAgICBiYXRjaF9wcm9jZXNzb3I6IEJhdGNoUHJvY2Vzc29yLAogICAgYmF0Y2hlc19xdWV1ZTogUXVldWUsCiAgICB0YXNrc19xdWV1ZTogUXVldWUsCiAgICBuX3Rhc2tfY29tcGxldGVyczogaW50LAopOgogICAgIiIiCiAgICBQcm9jZXNzIHRoZSBiYXRjaGVzIGluIHRoZSBnaXZlbiBiYXRjaGVzIHF1ZXVlIGFuZCBwdXQgdGhlIHRhc2tzIGluIHRoZSBnaXZlbiB0YXNrcyBxdWV1ZS4gVGhlIGZ1bmN0aW9uIHdpbGwgc3RvcAogICAgd2hlbiB0aGUgZ2l2ZW4gYmF0Y2hlcyBxdWV1ZSB3aWxsIHJlY2VpdmUgdGhlIHN0b3AgbWFyay4gSXQgaXMgYWltZWQgdG8gYmUgdXNlZCB3aXRoIG11bHRpcHJvY2Vzc2luZyBhcyBhIHByb2Nlc3MuCgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogICBBIGJhdGNoIHByb2Nlc3NvciB0byBwcm9jZXNzIHRoZSBiYXRjaGVzLgogICAgOnBhcmFtIGJhdGNoZXNfcXVldWU6ICAgICBBIHF1ZXVlIHRvIGdldCB0aGUgYmF0Y2hlcyBmcm9tLgogICAgOnBhcmFtIHRhc2tzX3F1ZXVlOiAgICAgICBBIHF1ZXVlIHRvIHB1dCB0aGUgdGFza3MgaW4uCiAgICA6cGFyYW0gbl90YXNrX2NvbXBsZXRlcnM6IFRoZSBudW1iZXIgb2YgdGFzayBjb21wbGV0ZXJzIChwcm9jZXNzZXMgdGhhdCBydW4gdGhlIGBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbikuIEEgc3RvcCBtYXJrIHdpbGwgYmUgc2VudCB0byB0aGUgdGFza3MgcXVldWUgZm9yIGVhY2ggdGFzayBjb21wbGV0ZXIuCiAgICAiIiIKICAgIHdoaWxlIFRydWU6CiAgICAgICAgIyBHZXQgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoOiBMaXN0W2RpY3RdID0gYmF0Y2hlc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIGJhdGNoID09IF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLOgogICAgICAgICAgICBicmVhawoKICAgICAgICAjIFByb2Nlc3MgdGhlIGJhdGNoOgogICAgICAgIGJhdGNoX3Byb2Nlc3Nvci5wcm9jZXNzX2JhdGNoKGJhdGNoPWJhdGNoKQoKICAgICAgICAjIEdldCB0aGUgdGFza3M6CiAgICAgICAgdGFza3MgPSBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Rhc2tzKCkKCiAgICAgICAgIyBRdWV1ZSB0aGUgdGFza3M6CiAgICAgICAgZm9yIHRhc2sgaW4gdGFza3M6CiAgICAgICAgICAgIHRhc2tzX3F1ZXVlLnB1dCh0YXNrLnRvX3R1cGxlKCkpCgogICAgIyBNYXJrIHRoZSBlbmQgb2YgdGhlIGJhdGNoZXM6CiAgICBmb3IgXyBpbiByYW5nZShuX3Rhc2tfY29tcGxldGVycyk6CiAgICAgICAgdGFza3NfcXVldWUucHV0KF9NVUxUSVBST0NFU1NJTkdfU1RPUF9NQVJLKQoKCmRlZiBfbXVsdGlwcm9jZXNzaW5nX2NvbXBsZXRlX3Rhc2tzKHRhc2tzX3F1ZXVlOiBRdWV1ZSwgcmVzdWx0c19xdWV1ZTogUXVldWUpOgogICAgIiIiCiAgICBDb21wbGV0ZSB0aGUgdGFza3MgaW4gdGhlIGdpdmVuIHF1ZXVlIGFuZCBwdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGdpdmVuIHJlc3VsdHMgcXVldWUuIFRoZSBmdW5jdGlvbiB3aWxsIHN0b3Agd2hlbgogICAgdGhlIGdpdmVuIHRhc2tzIHF1ZXVlIHdpbGwgcmVjZWl2ZSB0aGUgc3RvcCBtYXJrLiBJdCBpcyBhaW1lZCB0byBiZSB1c2VkIHdpdGggbXVsdGlwcm9jZXNzaW5nIGFzIGEgcHJvY2Vzcy4KCiAgICA6cGFyYW0gdGFza3NfcXVldWU6ICAgQSBxdWV1ZSB0byBnZXQgdGhlIHRhc2tzIGZyb20uCiAgICA6cGFyYW0gcmVzdWx0c19xdWV1ZTogQSBxdWV1ZSB0byBwdXQgdGhlIHJlc3VsdHMgaW4uCiAgICAiIiIKICAgIHRhc2tzX21hcCA9IHsKICAgICAgICBCYXNlVGFzay5fX25hbWVfXzogQmFzZVRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25UYXNrLl9fbmFtZV9fOiBTcGVlY2hEaWFyaXphdGlvblRhc2ssCiAgICAgICAgU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzay5fX25hbWVfXzogU3BlZWNoRGlhcml6YXRpb25QZXJDaGFubmVsVGFzaywKICAgIH0KCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IHRoZSB0YXNrOgogICAgICAgIHRhc2sgPSB0YXNrc19xdWV1ZS5nZXQoKQogICAgICAgIGlmIHRhc2sgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIGJyZWFrCgogICAgICAgICMgUmVjb25zdHJ1Y3QgdGhlIHRhc2s6CiAgICAgICAgdGFza19jbGFzcywgdGFza19rd2FyZ3MgPSB0YXNrCiAgICAgICAgdGFzayA9IHRhc2tzX21hcFt0YXNrX2NsYXNzXSgqKnRhc2tfa3dhcmdzKQoKICAgICAgICAjIENvbXBsZXRlIHRoZSB0YXNrOgogICAgICAgIHRhc2suZG9fdGFzaygpCiAgICAgICAgcmVzdWx0c19xdWV1ZS5wdXQoKHRhc2suaXNfZmFpbGVkKCksIHRhc2suZ2V0X3Jlc3VsdCgpKSkKCiAgICAjIE1hcmsgdGhlIGVuZCBvZiB0aGUgdGFza3M6CiAgICByZXN1bHRzX3F1ZXVlLnB1dChfTVVMVElQUk9DRVNTSU5HX1NUT1BfTUFSSykKCgojIEdldCB0aGUgZ2xvYmFsIGxvZ2dlcjoKX0xPR0dFUiA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCkKCgpkZWYgb3Blbl9tcGlfaGFuZGxlcigKICAgIHdvcmtlcl9pbnB1dHM6IExpc3Rbc3RyXSwgcm9vdF93b3JrZXJfaW5wdXRzOiBEaWN0W3N0ciwgQW55XSA9IE5vbmUKKToKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBDaGVjayBmb3IgTUxSdW4gYW5kIE9wZW5NUEkgYXZhaWxhYmlsaXR5OgogICAgY29udGV4dCwgY29tbSA9IF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKQoKICAgICMgQ2hlY2sgaWYgTUxSdW4gaXMgYXZhaWxhYmxlLCBzZXQgdGhlIGdsb2JhbCBsb2dnZXIgdG8gTUxSdW4nczoKICAgIGlmIGNvbnRleHQ6CiAgICAgICAgX0xPR0dFUiA9IGNvbnRleHQubG9nZ2VyCgogICAgZGVmIGRlY29yYXRvcihoYW5kbGVyKToKICAgICAgICBpZiBjb21tIGlzIE5vbmUgb3IgY29tbS5HZXRfc2l6ZSgpID09IDE6CiAgICAgICAgICAgIHJldHVybiBoYW5kbGVyCgogICAgICAgIEB3cmFwcyhoYW5kbGVyKQogICAgICAgIGRlZiB3cmFwcGVyKCoqa3dhcmdzKToKICAgICAgICAgICAgIyBHZXQgdGhlIG9wZW4gbXBpIGVudmlyb25tZW50IHByb3BlcnRpZXM6CiAgICAgICAgICAgIHNpemUgPSBjb21tLkdldF9zaXplKCkKICAgICAgICAgICAgcmFuayA9IGNvbW0uR2V0X3JhbmsoKQoKICAgICAgICAgICAgIyBHaXZlIHRoZSBjb3JyZWN0IGNodW5rIG9mIHRoZSB3b3JrZXJzIGlucHV0czoKICAgICAgICAgICAgZm9yIHdvcmtlcl9pbnB1dCBpbiB3b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBrd2FyZ3Nbd29ya2VyX2lucHV0XQogICAgICAgICAgICAgICAgaWYgaW5wdXRfYXJndW1lbnQgaXMgTm9uZToKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgc3RyKToKICAgICAgICAgICAgICAgICAgICBpbnB1dF9hcmd1bWVudCA9IF9nZXRfYXVkaW9fZmlsZXMoCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfcGF0aD1QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTYXZlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IG9mIHRoaXMgd29ya2VyOgogICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gUGF0aChvdXRwdXRbMF0pCgogICAgICAgICAgICAjIFNlbmQgdGhlIG91dHB1dCB0byB0aGUgcm9vdCByYW5rIChyYW5rICMwKToKICAgICAgICAgICAgb3V0cHV0ID0gY29tbS5nYXRoZXIob3V0cHV0LCByb290PTApCgogICAgICAgICAgICAjIEpvaW4gdGhlIGRhdGEgZnJvbSBhbGwgd29ya2VyczoKICAgICAgICAgICAgaWYgcmFuayA9PSAwOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuaW5mbygiQ29sbGVjdGluZyBkYXRhIGZyb20gd29ya2VycyB0byByb290IHdvcmtlci4iKQoKICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXM6CiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3JpZXMgPSBzZXQoW1BhdGgob3V0X2RpcikgZm9yIG91dF9kaXIsIF8sIF8gaW4gb3V0cHV0XSkKICAgICAgICAgICAgICAgIGZvciByIGluIHJhbmdlKDEsIHNpemUpOgogICAgICAgICAgICAgICAgICAgICMgVHJ1ZSBtZWFucyB0aGUgb3RoZXIgd29ya2VycyBzaG91bGQgcGFzcyB0aGVpciBmaWxlcyB0byB0aGUgcm9vdCB3b3JrZXIgKHJhbmsgMCk6CiAgICAgICAgICAgICAgICAgICAgY29tbS5zZW5kKGxlbihvdXRwdXRfZGlyZWN0b3JpZXMpICE9IDEsIGRlc3Q9cikKCiAgICAgICAgICAgICAgICAjIElmIHRoZXJlIGFyZSBkaWZmZXJlbnQgb3V0cHV0IGRpcmVjdG9yaWVzLCBsaXN0ZW4gdG8gdGhlIG90aGVyIHdvcmtlcnM6CiAgICAgICAgICAgICAgICBpZiBsZW4ob3V0cHV0X2RpcmVjdG9yaWVzKSAhPSAxOgogICAgICAgICAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgZmlsZXMgZnJvbSB0aGUgb3RoZXIgd29ya2VyczoKICAgICAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICAgICAgZm9yIHIgaW4gcmFuZ2UoMSwgc2l6ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVzLmV4dGVuZChjb21tLnJlY3Yoc291cmNlPXIpKQogICAgICAgICAgICAgICAgICAgICMgV3JpdGUgdGhlIGZpbGVzIHRvIHRoZSByb290IHdvcmtlcidzIG91dHB1dCBkaXJlY3Rvcnk6CiAgICAgICAgICAgICAgICAgICAgZm9yIGZpbGVfbmFtZSwgZmlsZV9jb250ZW50IGluIGZpbGVzOgogICAgICAgICAgICAgICAgICAgICAgICB3aXRoIG9wZW4ob3V0cHV0X2RpcmVjdG9yeSAvIGZpbGVfbmFtZSwgInciKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgZi53cml0ZShmaWxlX2NvbnRlbnQpCgogICAgICAgICAgICAgICAgIyBDb25jYXRlbmF0ZSB0aGUgZGF0YWZyYW1lczoKICAgICAgICAgICAgICAgIGRhdGFmcmFtZSA9IHBkLmNvbmNhdChvYmpzPVtkZiBmb3IgXywgZGYsIF8gaW4gb3V0cHV0XSwgYXhpcz0wKQoKICAgICAgICAgICAgICAgICMgQ29uY2F0ZW5hdGUgdGhlIGVycm9ycyBkaWN0aW9uYXJpZXM6CiAgICAgICAgICAgICAgICBlcnJvcnNfZGljdGlvbmFyeSA9IHJlZHVjZSgKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvci5pb3IsIFtlcnIgZm9yIF8sIF8sIGVyciBpbiBvdXRwdXRdLCB7fQogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIGRhdGFmcmFtZSwgZXJyb3JzX2RpY3Rpb25hcnkKCiAgICAgICAgICAgICMgTGlzdGVuIHRvIHJhbmsgMCB0byBzZWUgaWYgdGhlcmUgYXJlIGRpZmZlcmVudCBvdXRwdXQgZGlyZWN0b3JpZXMgYW5kIHRoaXMgcmFuayBuZWVkIHRvIHNlbmQgaXRzIGZpbGVzIHRvCiAgICAgICAgICAgICMgaXQ6CiAgICAgICAgICAgIGlmIGNvbW0ucmVjdihzb3VyY2U9MCk6CiAgICAgICAgICAgICAgICBmaWxlcyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgZmlsZSBpbiBvcy5saXN0ZGlyKG91dHB1dF9kaXJlY3RvcnkpOgogICAgICAgICAgICAgICAgICAgIHdpdGggb3BlbihvdXRwdXRfZGlyZWN0b3J5IC8gZmlsZSwgInIiKSBhcyBmOgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlcy5hcHBlbmQoKGZpbGUsIGYucmVhZCgpKSkKICAgICAgICAgICAgICAgIGNvbW0uc2VuZChmaWxlcywgZGVzdD0wKQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpkZWYgX2NoZWNrX21scnVuX2FuZF9vcGVuX21waSgpIC0+IFR1cGxlWyJtbHJ1bi5NTENsaWVudEN0eCIsICJtcGk0cHkuTVBJLkludHJhY29tbSJdOgogICAgaXNfbXBpID0gRmFsc2UKICAgIHRyeToKICAgICAgICBpbXBvcnQgbWxydW4KCiAgICAgICAgY29udGV4dCA9IG1scnVuLmdldF9vcl9jcmVhdGVfY3R4KG5hbWU9Im1scnVuIikKICAgICAgICBpc19tcGkgPSBjb250ZXh0LmxhYmVscy5nZXQoImtpbmQiLCAiam9iIikgPT0gIm1waWpvYiIKCiAgICAgICAgaWYgaXNfbXBpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBmcm9tIG1waTRweSBpbXBvcnQgTVBJCgogICAgICAgICAgICAgICAgcmV0dXJuIGNvbnRleHQsIE1QSS5DT01NX1dPUkxECiAgICAgICAgICAgIGV4Y2VwdCBNb2R1bGVOb3RGb3VuZEVycm9yIGFzIG1waTRweV9ub3RfZm91bmQ6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5lcnJvcigKICAgICAgICAgICAgICAgICAgICAiVG8gZGlzdHJpYnV0ZSB0aGUgZnVuY3Rpb24gdXNpbmcgTUxSdW4ncyAnbXBpam9iJyB5b3UgbmVlZCB0byBoYXZlIGBtcGk0cHlgIHBhY2thZ2UgaW4geW91ciAiCiAgICAgICAgICAgICAgICAgICAgImludGVycHJldGVyLiBQbGVhc2UgcnVuIGBwaXAgaW5zdGFsbCBtcGk0cHlgIGFuZCBtYWtlIHN1cmUgeW91IGhhdmUgb3Blbi1tcGkuIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgcmFpc2UgbXBpNHB5X25vdF9mb3VuZAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBOb25lCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtb2R1bGVfbm90X2ZvdW5kOgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgcmFpc2UgbW9kdWxlX25vdF9mb3VuZAogICAgcmV0dXJuIE5vbmUsIE5vbmUKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zY3JpYmUoCiAgICAjIElucHV0IC8gT3V0cHV0IGt3YXJnczoKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBQYXRoLCBMaXN0W1VuaW9uW3N0ciwgUGF0aF1dXSwKICAgIG91dHB1dF9kaXJlY3Rvcnk6IHN0ciA9IE5vbmUsCiAgICAjIE1vZGVsIGxvYWRpbmcga3dhcmdzOgogICAgbW9kZWxfbmFtZTogc3RyID0gIm9wZW5haS93aGlzcGVyLXRpbnkiLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yOiBib29sID0gTm9uZSwKICAgIHVzZV9iZXR0ZXJfdHJhbnNmb3JtZXJzOiBib29sID0gTm9uZSwKICAgICMgR2VuZXJhdGlvbiBrd2FyZ3M6CiAgICBhc3Npc3RhbnRfbW9kZWw6IHN0ciA9IE5vbmUsCiAgICBtYXhfbmV3X3Rva2VuczogaW50ID0gMTI4LAogICAgY2h1bmtfbGVuZ3RoX3M6IGludCA9IDMwLAogICAgYmF0Y2hfc2l6ZTogaW50ID0gOCwKICAgIHNwb2tlbl9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoOiBib29sID0gRmFsc2UsCiAgICAjIERpYXJpemF0aW9uIGt3YXJnczoKICAgIHNwZWVjaF9kaWFyaXphdGlvbjogRGljdFtzdHIsIExpc3RbVHVwbGVbZmxvYXQsIGZsb2F0LCBzdHJdXV0gPSBOb25lLAogICAgc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IGludCA9IE5vbmUsCiAgICBzcGVha2VyX2xhYmVsczogTGlzdFtzdHJdID0gTm9uZSwKICAgICMgT3RoZXIga3dhcmdzOgogICAgdXNlX211bHRpcHJvY2Vzc2luZzogVW5pb25bYm9vbCwgaW50XSA9IEZhbHNlLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopOgogICAgIiIiCiAgICBUcmFuc2NyaWJlIGF1ZGlvIGZpbGVzIGludG8gdGV4dCBmaWxlcyBhbmQgY29sbGVjdCBhZGRpdGlvbmFsIGRhdGEuIFRoZSBlbmQgcmVzdWx0IGlzIGEgZGlyZWN0b3J5IG9mIHRyYW5zY3JpYmVkCiAgICB0ZXh0IGZpbGVzIGFuZCBhIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIGF1ZGlvX2ZpbGUgLSBUaGUgYXVkaW8gZmlsZSBwYXRoLgogICAgKiB0cmFuc2NyaXB0aW9uX2ZpbGUgLSBUaGUgdHJhbnNjcmliZWQgdGV4dCBmaWxlIG5hbWUgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkuCgogICAgVGhlIHRyYW5zY3JpcHRpb24gaXMgYmFzZWQgb24gSHVnZ2luZ2ZhY2UncyBBU1IgcGlwZWxpbmUgLQogICAgaHR0cHM6Ly9odWdnaW5nZmFjZS5jby90cmFuc2Zvcm1lcnMvbWFpbl9jbGFzc2VzL3BpcGVsaW5lcy5odG1sI3RyYW5zZm9ybWVycy5BdXRvbWF0aWNTcGVlY2hSZWNvZ25pdGlvblBpcGVsaW5lIGFuZAogICAgaXMgdGVzdGVkIHdpdGggT3BlbkFJJ3MgV2hpc3BlciBtb2RlbHMgLSBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haS4KCiAgICBJZiBvbmUgb2YgdGhlIHNwZWFrZXIgZGlhcml6YXRpb24gcGFyYW1ldGVycyBhcmUgZ2l2ZW4gKGVpdGhlciBgc3BlZWNoX2RpYXJpemF0aW9uYCBvcgogICAgYHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsYCksIHRoZSB0cmFuc2NyaXB0aW9uIHdpbGwgYmUgd3JpdHRlbiBpbiBhIGNvbnZlcnNhdGlvbiBmb3JtYXQsIHdoZXJlIGVhY2ggc3BlYWtlciB3aWxsCiAgICBiZSB3cml0dGVuIGluIGEgc2VwYXJhdGUgbGluZTo6CgogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIHNwZWFrZXJfMjogdGV4dAogICAgICAgIHNwZWFrZXJfMTogdGV4dAogICAgICAgIC4uLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6ICAgICAgICAgICAgICAgICAgQSBkaXJlY3Rvcnkgb2YgYXVkaW8gZmlsZXMgb3IgYSBzaW5nbGUgZmlsZSBvciBhIGxpc3Qgb2YgZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgICAgICAgICAgUGF0aCB0byBhIGRpcmVjdG9yeSB0byBzYXZlIGFsbCB0cmFuc2NyaWJlZCBhdWRpbyBmaWxlcy4gSWYgbm90IGdpdmVuLCB3aWxsIHNhdmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIHRyYW5zY3JpYmVkIGZpbGVzIGluIGEgdGVtcG9yYXJ5IGRpcmVjdG9yeS4KICAgIDpwYXJhbSBtb2RlbF9uYW1lOiAgICAgICAgICAgICAgICAgVGhlIG1vZGVsIG5hbWUgdG8gdXNlLiBTaG91bGQgYmUgYSBtb2RlbCBmcm9tIHRoZSBPcGVuQUkncyBXaGlzcGVyIG1vZGVscyBmb3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVzdCByZXN1bHRzIChmb3IgZXhhbXBsZSAidGlueSIsICJiYXNlIiwgImxhcmdlIiwgZXRjLikuIFNlZSBoZXJlIGZvciBtb3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZm9ybWF0aW9uOiBodHRwczovL2h1Z2dpbmdmYWNlLmNvL29wZW5haT9zZWFyY2hfbW9kZWxzPXdoaXNwZXIuCiAgICA6cGFyYW0gZGV2aWNlOiAgICAgICAgICAgICAgICAgICAgIFRoZSBkZXZpY2UgdG8gdXNlIGZvciBpbmZlcmVuY2UuIElmIG5vdCBnaXZlbiwgd2lsbCB1c2UgR1BVIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSB1c2VfZmxhc2hfYXR0ZW50aW9uXzI6ICAgICAgV2hldGhlciB0byB1c2UgdGhlIEZsYXNoIEF0dGVudGlvbiAyIGltcGxlbWVudGF0aW9uLiBJdCBjYW4gYmUgdXNlZCBvbmx5IHdpdGgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb25lIG9mIHRoZSBmb2xsb3dpbmcgR1BVczogTnZpZGlhIEggc2VyaWVzIGFuZCBOdmlkaWEgQSBzZXJpZXMuIFQ0IHN1cHBvcnQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSBhdmFpbGFibGUgc29vbi4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IElmIGJvdGggYHVzZV9mbGFzaF9hdHRlbnRpb25fMmAgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGB1c2VfYmV0dGVyX3RyYW5zZm9ybWVyc2AgYXJlIGBOb25lYCwgdGhlIG9wdGltaXphdGlvbiB3aWxsIGJlIGNob3NlbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdXRvbWF0aWNhbGx5IGFjY29yZGluZyB0byB0aGUgYXZhaWxhYmxlIHJlc291cmNlcy4KCiAgICA6cGFyYW0gdXNlX2JldHRlcl90cmFuc2Zvcm1lcnM6ICAgIFdoZXRoZXIgdG8gdXNlIHRoZSBCZXR0ZXIgVHJhbnNmb3JtZXJzIGxpYnJhcnkgdG8gZnVydGhlciBvcHRpbWl6ZSB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3VsZCBiZSB1c2VkIGZvciBhbGwgdXNlIGNhc2VzIHRoYXQgZG8gbm90IHN1cHBvcnQgZmxhc2ggYXR0ZW50aW9uIDIuCgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb3RlOiBJZiBib3RoIGB1c2VfZmxhc2hfYXR0ZW50aW9uXzJgIGFuZCBgdXNlX2JldHRlcl90cmFuc2Zvcm1lcnNgIGFyZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgTm9uZWAsIHRoZSBvcHRpbWl6YXRpb24gd2lsbCBiZSBjaG9zZW4gYXV0b21hdGljYWxseSBhY2NvcmRpbmcgdG8gdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZSByZXNvdXJjZXMuCiAgICA6cGFyYW0gYXNzaXN0YW50X21vZGVsOiAgICAgICAgICAgIFRoZSBhc3Npc3RhbnQgbW9kZWwgbmFtZSB0byB1c2UgZm9yIGluZmVyZW5jZS4gTm90aWNlIHRoYXQgdGhlIG9wdGltaXphdGlvbnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZsYXNoIGF0dGVudGlvbiAyIGFuZCBiZXR0ZXIgdHJhbnNmb3JtZXJzKSB3aWxsIGJlIGFwcGxpZWQgZm9yIHRoZSBhc3Npc3RhbnQgYXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VsbC4gU2hvdWxkIGJlIGEgbW9kZWwgZnJvbSBIdWdnaW5nZmFjZSdzIGRpc3RpbC13aGlzcGVyIChzZWUgaGVyZSBmb3IgbW9yZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmZvcm1hdGlvbjogaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2Rpc3RpbC13aGlzcGVyKS4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5vdGU6IEN1cnJlbnRseSBhbiBhc3Npc3RhbnQgbW9kZWwgaXMgb25seSB1c2FibGUgd2l0aCBiYXRjaCBzaXplIG9mIDEuCiAgICA6cGFyYW0gbWF4X25ld190b2tlbnM6ICAgICAgICAgICAgIFRoZSBtYXhpbXVtIG51bWJlciBvZiBuZXcgdG9rZW5zIHRvIGdlbmVyYXRlLiBUaGlzIGlzIHVzZWQgdG8gbGltaXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRpb24gbGVuZ3RoLiBEZWZhdWx0IGlzIDEyOCB0b2tlbnMuCiAgICA6cGFyYW0gY2h1bmtfbGVuZ3RoX3M6ICAgICAgICAgICAgIFRoZSBhdWRpbyBjaHVuayB0byBzcGxpdCB0aGUgYXVkaW8gdG8gKGluIHNlY29uZHMpLiBEZWZhdWx0IGlzIDMwIHNlY29uZHMuCiAgICA6cGFyYW0gYmF0Y2hfc2l6ZTogICAgICAgICAgICAgICAgIFRoZSBiYXRjaCBzaXplIHRvIHVzZSBmb3IgaW5mZXJlbmNlLiBEZWZhdWx0IGlzIDIuCiAgICA6cGFyYW0gc3Bva2VuX2xhbmd1YWdlOiAgICAgICAgICAgIEFpbSB3aGlzcGVyIHRvIGtub3cgd2hhdCBsYW5ndWFnZSBpcyBzcG9rZW4uIElmIE5vbmUsIGl0IHdpbGwgdHJ5IHRvIGRldGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdC4KICAgIDpwYXJhbSB0cmFuc2xhdGVfdG9fZW5nbGlzaDogICAgICAgV2hldGhlciB0byB0cmFuc2xhdGUgdGhlIHRyYW5zY3JpcHRpb25zIHRvIEVuZ2xpc2guCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemF0aW9uOiAgICAgICAgIEEgc3BlZWNoIGRpYXJpemF0aW9uIGRpY3Rpb25hcnkgd2l0aCB0aGUgZmlsZSBuYW1lcyB0byB0cmFuc2NyaWJlIGFzIGtleXMgYW5kCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZWlyIGRpYXJpemF0aW9uIGFzIHZhbHVlLiBUaGUgZGlhcml6YXRpb24gaXMgYSBsaXN0IG9mIHR1cGxlczoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHN0YXJ0LCBlbmQsIHNwZWFrZXIpLiBBbiBleGFtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBhIGRpYXJpemF0aW9uIGRpY3Rpb25hcnk6OgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF1ZGlvX2ZpbGVfbmFtZSI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGFydCI6IDAuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImVuZCI6IDIuMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWFrZXIiOiAiQWdlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAyLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbmQiOiA0LjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzcGVha2VyIjogIkNsaWVudCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTm90ZTogVGhlIGRpYXJpemF0aW9uIG11c3QgYmUgZm9yIHRoZSBlbnRpcmUgZHVyYXRpb24gb2YgdGhlIGF1ZGlvIGZpbGUgKGFzIGxvbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMgV2hpc3BlciBpcyBwcmVkaWN0aW5nIHdvcmRzIHVwIHVudGlsIHRoZW4uCiAgICA6cGFyYW0gc3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWw6IFBlcmZvcm0gc3BlZWNoIGRpYXJpemF0aW9uIHBlciBjaGFubmVsLiBFYWNoIHNwZWFrZXIgaXMgZXhwZWN0ZWQgdG8gYmVsb25nIHRvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgc2VwYXJhdGUgY2hhbm5lbCBpbiB0aGUgYXVkaW8uIE5vdGljZTogVGhpcyB3aWxsIG1ha2UgdGhlIHRyYW5zY3JpcHRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvd2VyIGFzIGVhY2ggY2hhbm5lbCB3aWwgYmUgdHJhbnNjcmliZWQgc2VwYXJhdGx5LiBJZiBhIHNwZWVjaCBkaWFyaXphdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcyBwYXNzZWQgKHZpYSB0aGUgYHNwZWVjaF9kaWFyaXphdGlvbmAgcGFyYW1ldGVyKSwgdGhpcyBwYXJhbWV0ZXIgaXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWdub3JlZC4KICAgIDpwYXJhbSBzcGVha2VyX2xhYmVsczogICAgICAgICAgICAgQSBsaXN0IG9mIHNwZWFrZXIgbGFiZWxzIGJ5IGNoYW5uZWwgb3JkZXIgdG8gdXNlIGZvciB3cml0aW5nIHRoZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2NyaXB0aW9uIHdpdGggcmVzcGVjdCB0byBwZXIgY2hhbm5lbCBzcGVlY2ggZGlhcml6YXRpb24uIFRoaXMgd29uJ3QgYmUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlZCB0b2dldGhlciB3aXRoIGEgZ2l2ZW4gc3BlZWNoIGRpYXJpemF0aW9uICh2aWEgdGhlIGBzcGVlY2hfZGlhcml6YXRpb25gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcikuCiAgICA6cGFyYW0gdXNlX211bHRpcHJvY2Vzc2luZzogICAgICAgIFdoZXRoZXIgdG8gdXNlIG11bHRpcHJvY2Vzc2luZyB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlcy4gQ2FuIGJlIGVpdGhlciBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb2xlYW4gdmFsdWUgb3IgYW4gaW50ZWdlci4gSWYgYFRydWVgLCB3aWxsIHVzZSB0aGUgZGVmYXVsdCBhbW91bnQgb2Ygd29ya2VycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMyk6IDEgZm9yIHRyYW5zY3JpcHRpb24sIDEgZm9yIGJhdGNoIHByb2Nlc3NpbmcgYW5kIDEgZm9yIHRhc2sgY29tcGxldGlvbiAoc3VjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcyBzcGVlY2ggZGlhcml6YXRpb24gYW5kIHdyaXRpbmcgdG8gZmlsZXMpLiBUbyBjb250cm9sIHRoZSBhbW91bnQgb2YgdGFza3MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tcGxldGlvbiB3b3JrZXJzLCBhbiBpbnRlZ2VyIGNhbiBiZSBwcm92aWRlZCB0byBzcGVjaWZ5IHRoZSBhbW91bnQgb2Ygd29ya2Vycy4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYEZhbHNlYCwgd2lsbCB1c2UgYSBzaW5nbGUgcHJvY2Vzcy4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgICAgICAgICAgICBXaGV0aGVyIHRvIHByaW50IHRoZSBwcm9ncmVzcyBvZiB0aGUgdHJhbnNjcmlwdGlvbi4gRGVmYXVsdCBpcyBgRmFsc2VgLgogICAgIiIiCiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgR2V0IHRoZSBpbnB1dCBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgYXVkaW8gZmlsZXMuIikKICAgIGF1ZGlvX2ZpbGVzID0gX2dldF9hdWRpb19maWxlcyhkYXRhX3BhdGg9ZGF0YV9wYXRoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbihhdWRpb19maWxlcyl9IGF1ZGlvIGZpbGVzLiIpCgogICAgIyBHZXQgdGhlIG91dHB1dCBkaXJlY3Rvcnk6CiAgICBpZiBvdXRwdXRfZGlyZWN0b3J5IGlzIE5vbmU6CiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgX0xPR0dFUi5pbmZvKCJObyBvdXRwdXQgZGlyZWN0b3J5IGdpdmVuLCB1c2luZyB0ZW1wb3JhcnkgZGlyZWN0b3J5LiIpCiAgICAgICAgb3V0cHV0X2RpcmVjdG9yeSA9IHRlbXBmaWxlLm1rZHRlbXAoKQogICAgb3V0cHV0X2RpcmVjdG9yeSA9IFBhdGgob3V0cHV0X2RpcmVjdG9yeSkuYWJzb2x1dGUoKQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIlRyYW5zY3JpcHRpb25zIHdpbGwgYmUgc2F2ZWQgdG86IHtvdXRwdXRfZGlyZWN0b3J5fSIpCgogICAgIyBJbml0aWFsaXplIGEgYmF0Y2ggcHJvY2Vzc29yIGFjY29yZGluZyB0byB1c2VyIHJlcXVpcmVtZW50cyAobm8gc3BlZWNoIGRpYXJpemF0aW9uLCBnaXZlbiBzcGVlY2ggZGlhcml6YXRpb24sCiAgICAjIHNwZWVjaCBkaWFyaXphdGlvbiBwZXIgY2hhbm5lbCk6CiAgICBpZiBzcGVlY2hfZGlhcml6YXRpb246CiAgICAgICAgYmF0Y2hfcHJvY2Vzc29yID0gU3BlZWNoRGlhcml6YXRpb25CYXRjaFByb2Nlc3NvcigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgc3BlZWNoX2RpYXJpemF0aW9uPXNwZWVjaF9kaWFyaXphdGlvbiwKICAgICAgICApCiAgICBlbGlmIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IFBlckNoYW5uZWxTcGVlY2hEaWFyaXphdGlvbkJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICAgICBuX2NoYW5uZWxzPXNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsLAogICAgICAgICAgICBzcGVha2Vycz1zcGVha2VyX2xhYmVscywKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIGJhdGNoX3Byb2Nlc3NvciA9IEJhdGNoUHJvY2Vzc29yKAogICAgICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICAgICAgb3V0cHV0X2RpcmVjdG9yeT1vdXRwdXRfZGlyZWN0b3J5LAogICAgICAgICkKCiAgICAjIEluaXRpYWxpemUgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICB0cmFuc2NyaWJlciA9IFRyYW5zY3JpYmVyKAogICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgdXNlX2ZsYXNoX2F0dGVudGlvbl8yPXVzZV9mbGFzaF9hdHRlbnRpb25fMiwKICAgICAgICB1c2VfYmV0dGVyX3RyYW5zZm9ybWVycz11c2VfYmV0dGVyX3RyYW5zZm9ybWVycywKICAgICAgICBhc3Npc3RhbnRfbW9kZWw9YXNzaXN0YW50X21vZGVsLAogICAgICAgIG1vZGVsX25hbWU9bW9kZWxfbmFtZSwKICAgICAgICBtYXhfbmV3X3Rva2Vucz1tYXhfbmV3X3Rva2VucywKICAgICAgICBjaHVua19sZW5ndGhfcz1jaHVua19sZW5ndGhfcywKICAgICAgICBiYXRjaF9zaXplPWJhdGNoX3NpemUsCiAgICAgICAgcmV0dXJuX3RpbWVzdGFtcHM9KAogICAgICAgICAgICAid29yZCIKICAgICAgICAgICAgaWYgc3BlZWNoX2RpYXJpemF0aW9uIGlzIG5vdCBOb25lIG9yIHNwZWVjaF9kaWFyaXplX3Blcl9jaGFubmVsIGlzIG5vdCBOb25lCiAgICAgICAgICAgIGVsc2UgRmFsc2UKICAgICAgICApLAogICAgICAgIHBlcl9jaGFubmVsX3RyYW5zY3JpcHRpb249c3BlZWNoX2RpYXJpemVfcGVyX2NoYW5uZWwgb3IgMCwKICAgICAgICBzcG9rZW5fbGFuZ3VhZ2U9c3Bva2VuX2xhbmd1YWdlLAogICAgICAgIHRyYW5zbGF0ZV90b19lbmdsaXNoPXRyYW5zbGF0ZV90b19lbmdsaXNoLAogICAgKQoKICAgICMgUnVuIHRoZSB0cmFuc2NyaXB0aW9uOgogICAgaWYgdXNlX211bHRpcHJvY2Vzc2luZzoKICAgICAgICByZXN1bHRzID0gX3BhcmFsbGVsX3J1bigKICAgICAgICAgICAgbl93b3JrZXJzPXVzZV9tdWx0aXByb2Nlc3NpbmcKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh1c2VfbXVsdGlwcm9jZXNzaW5nLCBpbnQpCiAgICAgICAgICAgIGVsc2UgMSwKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQogICAgZWxzZToKICAgICAgICByZXN1bHRzID0gX3J1bigKICAgICAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsCiAgICAgICAgICAgIGJhdGNoX3Byb2Nlc3Nvcj1iYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgIHRyYW5zY3JpYmVyPXRyYW5zY3JpYmVyLAogICAgICAgICAgICB2ZXJib3NlPXZlcmJvc2UsCiAgICAgICAgKQoKICAgICMgUHJvY2VzcyB0aGUgcmVzdWx0czoKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKCJTdW1tYXJpemluZyB0aGUgcmVzdWx0cy4iKQogICAgc3VjY2Vzc2VzID0gW10KICAgIGVycm9ycyA9IHt9CiAgICBmb3IgaXNfZXJyb3IsIHJlc3VsdCBpbiByZXN1bHRzOgogICAgICAgIGlmIGlzX2Vycm9yOgogICAgICAgICAgICBlcnJvcnNbcmVzdWx0WzBdXSA9IHJlc3VsdFsxXQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHN1Y2Nlc3Nlcy5hcHBlbmQocmVzdWx0KQogICAgc3VjY2Vzc2VzID0gcGQuRGF0YUZyYW1lKHN1Y2Nlc3NlcywgY29sdW1ucz1bImF1ZGlvX2ZpbGUiLCAidHJhbnNjcmlwdGlvbl9maWxlIl0pCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygKICAgICAgICAgICAgZiJEb25lICh7c3VjY2Vzc2VzLnNoYXBlWzBdfS97bGVuKGF1ZGlvX2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNjcmlwdGlvbnMgc3VtbWFyeTpcbiIKICAgICAgICAgICAgZiJ7c3VjY2Vzc2VzLmhlYWQoKX0iCiAgICAgICAgKQoKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfYXVkaW9fZmlsZXMoCiAgICBkYXRhX3BhdGg6IFVuaW9uW1BhdGgsIHN0ciwgbGlzdF0sCikgLT4gTGlzdFtQYXRoXToKICAgICIiIgogICAgR2V0IHRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLiBJZiBhIHBhdGggdG8gYSBkaXJlY3RvcnkgaXMgZ2l2ZW4sIGFsbCBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IHdpbGwgYmUgY29sbGVjdGVkLgoKICAgIDpwYXJhbSBkYXRhX3BhdGg6IFRoZSBkYXRhIHBhdGggdG8gY29sbGVjdCB0aGUgYXVkaW8gZmlsZXMgZnJvbS4KCiAgICA6cmV0dXJuczogVGhlIGF1ZGlvIGZpbGVzIGxpc3QuCiAgICAiIiIKICAgICMgQ2hlY2sgaWYgZ2l2ZW4gYSBsaXN0IG9mIHBhdGhzOgogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIGxpc3QpOgogICAgICAgIGF1ZGlvX2ZpbGVzID0gW10KICAgICAgICBmb3IgcGF0aCBpbiBkYXRhX3BhdGg6CiAgICAgICAgICAgIGF1ZGlvX2ZpbGVzLmV4dGVuZChfZ2V0X2F1ZGlvX2ZpbGVzKGRhdGFfcGF0aD1wYXRoKSkKICAgICAgICByZXR1cm4gYXVkaW9fZmlsZXMKCiAgICAjIENoZWNrIGlmIGdpdmVuIGEgc2luZ2xlIHN0cmluZyBwYXRoIHRvIGNhc3QgaXQgdG8gYSBgcGF0aGxpYi5QYXRoYDoKICAgIGlmIGlzaW5zdGFuY2UoZGF0YV9wYXRoLCBzdHIpOgogICAgICAgIGRhdGFfcGF0aCA9IFBhdGgoZGF0YV9wYXRoKS5hYnNvbHV0ZSgpCgogICAgIyBDaGVjayBpZiB0aGUgcGF0aCBpcyBvZiBhIGRpcmVjdG9yeSBvciBhIGZpbGU6CiAgICBpZiBkYXRhX3BhdGguaXNfZGlyKCk6CiAgICAgICAgIyBHZXQgYWxsIGZpbGVzIGluc2lkZSB0aGUgZGlyZWN0b3J5OgogICAgICAgIGF1ZGlvX2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgYXVkaW9fZmlsZXMgPSBbZGF0YV9wYXRoXQogICAgZWxzZToKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICBmIlVucmVjb2duaXplZCBkYXRhIHBhdGguIFRoZSBwYXJhbWV0ZXIgYGRhdGFfcGF0aGAgbXVzdCBiZSBhIHZhbGlkIHBhdGggdG8gZWl0aGVyIGEgZGlyZWN0b3J5IHBhdGggb3IgYSAiCiAgICAgICAgICAgIGYiZmlsZS4gR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gYXVkaW9fZmlsZXMKCgpkZWYgX3J1bigKICAgIGF1ZGlvX2ZpbGVzOiBMaXN0W1BhdGhdLAogICAgYmF0Y2hfcHJvY2Vzc29yOiBCYXRjaFByb2Nlc3NvciwKICAgIHRyYW5zY3JpYmVyOiBUcmFuc2NyaWJlciwKICAgIHZlcmJvc2U6IGJvb2wsCikgLT4gTGlzdFtUdXBsZVtib29sLCBUdXBsZVtzdHIsIHN0cl1dXToKICAgICIiIgogICAgUnVuIHRoZSB0cmFuc2NyaXB0aW9uIHdpdGhvdXQgbXVsdGlwcm9jZXNzaW5nLgoKICAgIDpwYXJhbSBhdWRpb19maWxlczogICAgIFRoZSBhdWRpbyBmaWxlcyB0byB0cmFuc2NyaWJlLgogICAgOnBhcmFtIGJhdGNoX3Byb2Nlc3NvcjogVGhlIGJhdGNoIHByb2Nlc3NvciB0byB1c2UuCiAgICA6cGFyYW0gdHJhbnNjcmliZXI6ICAgICBUaGUgdHJhbnNjcmliZXIgdG8gdXNlLgogICAgOnBhcmFtIHZlcmJvc2U6ICAgICAgICAgVmVyYm9zaXR5LgoKICAgIDpyZXR1cm5zOiBUaGUgY29sbGVjdGVkIHJlc3VsdHMuCiAgICAiIiIKICAgICMgTG9hZCB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyB0aGUgdHJhbnNjcmlwdGlvbiBwaXBlbGluZS4iKQogICAgdHJhbnNjcmliZXIubG9hZCgpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbygiVHJhbnNjcmlwdGlvbiBwaXBlbGluZSBsb2FkZWQuIikKCiAgICAjIFRyYW5zY3JpYmUgdGhlIGZpbGVzOgogICAgdHJhbnNjcmliZXIudHJhbnNjcmliZSgKICAgICAgICBhdWRpb19maWxlcz1hdWRpb19maWxlcywKICAgICAgICBiYXRjaF9wcm9jZXNzb3I9YmF0Y2hfcHJvY2Vzc29yLAogICAgICAgIHZlcmJvc2U9dmVyYm9zZSwKICAgICkKCiAgICAjIFJldHVybiB0aGUgcmVzdWx0czoKICAgIHJldHVybiBiYXRjaF9wcm9jZXNzb3IuZ2V0X3Jlc3VsdHMoKQoKCmRlZiBfcGFyYWxsZWxfcnVuKAogICAgbl93b3JrZXJzOiBpbnQsCiAgICBhdWRpb19maWxlczogTGlzdFtQYXRoXSwKICAgIGJhdGNoX3Byb2Nlc3NvcjogQmF0Y2hQcm9jZXNzb3IsCiAgICB0cmFuc2NyaWJlcjogVHJhbnNjcmliZXIsCiAgICB2ZXJib3NlOiBib29sLAopOgogICAgIiIiCiAgICBSdW4gdGhlIHRyYW5zY3JpcHRpb24gd2l0aCBtdWx0aXByb2Nlc3NpbmcuCgogICAgOnBhcmFtIG5fd29ya2VyczogICAgICAgVGhlIGFtb3VudCBvZiB3b3JrZXJzIHRvIHVzZSBhcyB0YXNrIGNvbXBsZXRlcnMuCiAgICA6cGFyYW0gYXVkaW9fZmlsZXM6ICAgICBUaGUgYXVkaW8gZmlsZXMgdG8gdHJhbnNjcmliZS4KICAgIDpwYXJhbSBiYXRjaF9wcm9jZXNzb3I6IFRoZSBiYXRjaCBwcm9jZXNzb3IgdG8gdXNlLgogICAgOnBhcmFtIHRyYW5zY3JpYmVyOiAgICAgVGhlIHRyYW5zY3JpYmVyIHRvIHVzZS4KICAgIDpwYXJhbSB2ZXJib3NlOiAgICAgICAgIFZlcmJvc2l0eS4KCiAgICA6cmV0dXJuczogVGhlIGNvbGxlY3RlZCByZXN1bHRzLgogICAgIiIiCiAgICAjIEluaXRpYWxpemUgdGhlIG11bHRpcHJvY2Vzc2luZyBxdWV1ZXM6CiAgICBiYXRjaGVzX3F1ZXVlID0gUXVldWUoKQogICAgdGFza3NfcXVldWUgPSBRdWV1ZSgpCiAgICByZXN1bHRzX3F1ZXVlID0gUXVldWUoKQoKICAgICMgSW5pdGlhbGl6ZSB0aGUgbXVsdGlwcm9jZXNzaW5nIHByb2Nlc3NlczoKICAgIGJhdGNoX3Byb2Nlc3NpbmdfcHJvY2VzcyA9IFByb2Nlc3MoCiAgICAgICAgdGFyZ2V0PV9tdWx0aXByb2Nlc3NpbmdfcHJvY2Vzc19iYXRjaGVzLAogICAgICAgIGt3YXJncz17CiAgICAgICAgICAgICJiYXRjaF9wcm9jZXNzb3IiOiBiYXRjaF9wcm9jZXNzb3IsCiAgICAgICAgICAgICJiYXRjaGVzX3F1ZXVlIjogYmF0Y2hlc19xdWV1ZSwKICAgICAgICAgICAgInRhc2tzX3F1ZXVlIjogdGFza3NfcXVldWUsCiAgICAgICAgICAgICJuX3Rhc2tfY29tcGxldGVycyI6IG5fd29ya2VycywKICAgICAgICB9LAogICAgKQogICAgdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlcyA9IFsKICAgICAgICBQcm9jZXNzKAogICAgICAgICAgICB0YXJnZXQ9X211bHRpcHJvY2Vzc2luZ19jb21wbGV0ZV90YXNrcywKICAgICAgICAgICAga3dhcmdzPXsidGFza3NfcXVldWUiOiB0YXNrc19xdWV1ZSwgInJlc3VsdHNfcXVldWUiOiByZXN1bHRzX3F1ZXVlfSwKICAgICAgICApCiAgICAgICAgZm9yIF8gaW4gcmFuZ2Uobl93b3JrZXJzKQogICAgXQoKICAgICMgU3RhcnQgdGhlIG11bHRpcHJvY2Vzc2luZyBwcm9jZXNzZXM6CiAgICBiYXRjaF9wcm9jZXNzaW5nX3Byb2Nlc3Muc3RhcnQoKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLnN0YXJ0KCkKCiAgICAjIExvYWQgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmU6CiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIkxvYWRpbmcgdGhlIHRyYW5zY3JpcHRpb24gcGlwZWxpbmUuIikKICAgIHRyYW5zY3JpYmVyLmxvYWQoKQogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIlRyYW5zY3JpcHRpb24gcGlwZWxpbmUgbG9hZGVkLiIpCgogICAgIyBUcmFuc2NyaWJlIHRoZSBmaWxlczoKICAgIHRyYW5zY3JpYmVyLnRyYW5zY3JpYmUoCiAgICAgICAgYXVkaW9fZmlsZXM9YXVkaW9fZmlsZXMsIGJhdGNoZXNfcXVldWU9YmF0Y2hlc19xdWV1ZSwgdmVyYm9zZT12ZXJib3NlCiAgICApCgogICAgIyBDb2xsZWN0IHRoZSByZXN1bHRzOgogICAgcmVzdWx0cyA9IFtdCiAgICBzdG9wX21hcmtzX2NvdW50ZXIgPSAwCiAgICB3aGlsZSBUcnVlOgogICAgICAgICMgR2V0IGEgcmVzdWx0IGZyb20gdGhlIHF1ZXVlOgogICAgICAgIHJlc3VsdDogVHVwbGVbYm9vbCwgVHVwbGVbc3RyLCBzdHJdXSA9IHJlc3VsdHNfcXVldWUuZ2V0KCkKICAgICAgICBpZiByZXN1bHQgPT0gX01VTFRJUFJPQ0VTU0lOR19TVE9QX01BUks6CiAgICAgICAgICAgIHN0b3BfbWFya3NfY291bnRlciArPSAxCiAgICAgICAgICAgIGlmIHN0b3BfbWFya3NfY291bnRlciA9PSBuX3dvcmtlcnM6CiAgICAgICAgICAgICAgICBicmVhawogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgQ29sbGVjdCB0aGUgcmVzdWx0OgogICAgICAgICAgICByZXN1bHRzLmFwcGVuZChyZXN1bHQpCgogICAgIyBXYWl0IGZvciB0aGUgcHJvY2Vzc2VzIHRvIGZpbmlzaDoKICAgIHJlc3VsdHNfcXVldWUuZW1wdHkoKQogICAgYmF0Y2hfcHJvY2Vzc2luZ19wcm9jZXNzLmpvaW4oKQogICAgZm9yIHAgaW4gdGFza19jb21wbGV0aW9uX3Byb2Nlc3NlczoKICAgICAgICBwLmpvaW4oKQoKICAgIHJldHVybiByZXN1bHRz + disable_auto_mount: false + description: Transcribe audio files into text files + image: '' + command: '' + default_handler: transcribe entry_points: do_task: name: do_task doc: Try to perform the task storing an error if occurred. + lineno: 348 parameters: - name: self - outputs: [] - lineno: 348 has_varargs: false has_kwargs: false is_failed: name: is_failed doc: Check if the task failed. + lineno: 70 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: Whether the task failed. type: bool - lineno: 70 - has_varargs: false - has_kwargs: false get_result: name: get_result doc: 'Get the result of the task. If the task failed, the error will be returned, otherwise, the result will be the text file name.' + lineno: 78 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The task's result. type: Tuple[str, str] - lineno: 78 - has_varargs: false - has_kwargs: false to_tuple: name: to_tuple doc: Convert the task to a tuple to reconstruct it later (used for multiprocessing to pass in queue). + lineno: 358 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The converted task. type: Tuple[str, dict] - lineno: 358 - has_varargs: false - has_kwargs: false transcription_output_channels: name: transcription_output_channels doc: Get the transcription output channels. + lineno: 340 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The transcription output channels. type: List[Tuple[str, dict]] - lineno: 340 - has_varargs: false - has_kwargs: false process_batch: name: process_batch doc: 'Process a batch of transcriptions. Tasks related to the given batch will be created and stored in the batch processor.' + lineno: 575 parameters: - name: self - name: batch type: List[dict] doc: The batch of transcriptions to process. - outputs: [] - lineno: 575 has_varargs: false has_kwargs: false get_tasks: name: get_tasks doc: Get the tasks to perform. + lineno: 453 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The tasks to perform. type: List[BaseTask] - lineno: 453 - has_varargs: false - has_kwargs: false do_tasks: name: do_tasks doc: Perform the tasks. Should be used if no multiprocessing queue is given to a transcriber. + lineno: 463 parameters: - name: self - outputs: [] - lineno: 463 has_varargs: false has_kwargs: false get_results: name: get_results doc: Get the results of the tasks. The stored results are then cleared. + lineno: 471 parameters: - name: self + has_varargs: false + has_kwargs: false outputs: - doc: The results of the tasks. type: List[Tuple[bool, Tuple[str, str]]] - lineno: 471 - has_varargs: false - has_kwargs: false load: name: load doc: Load the transcriber. Must be called before transcribing. + lineno: 695 parameters: - name: self - outputs: [] - lineno: 695 has_varargs: false has_kwargs: false transcribe: @@ -184,6 +176,7 @@ \ a conversation format, where each speaker will\nbe written in a separate\ \ line::\n\n speaker_1: text\n speaker_2: text\n speaker_1: text\n\ \ ..." + lineno: 1097 parameters: - name: data_path type: Union[str, Path, List[Union[str, Path]]] @@ -276,69 +269,50 @@ type: bool doc: Whether to print the progress of the transcription. Default is `False`. default: false - outputs: [] - lineno: 1097 has_varargs: false has_kwargs: false audio_iterator: name: audio_iterator doc: '' - parameters: [] - outputs: - - type: Generator[Union[dict, str], None, None] lineno: 804 has_varargs: false has_kwargs: false + outputs: + - type: Generator[Union[dict, str], None, None] batch_iterator: name: batch_iterator doc: '' - parameters: [] - outputs: - - type: Generator[List[Union[dict, str]], None, None] lineno: 816 has_varargs: false has_kwargs: false + outputs: + - type: Generator[List[Union[dict, str]], None, None] open_mpi_handler: name: open_mpi_handler doc: '' + lineno: 957 parameters: - name: worker_inputs type: List[str] - name: root_worker_inputs type: Dict[str, Any] default: null - outputs: [] - lineno: 957 has_varargs: false has_kwargs: false decorator: name: decorator doc: '' + lineno: 969 parameters: - name: handler - outputs: [] - lineno: 969 has_varargs: false has_kwargs: false wrapper: name: wrapper doc: '' - parameters: [] - outputs: [] lineno: 974 has_varargs: false has_kwargs: true - description: Transcribe audio files into text files - default_handler: transcribe - disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} -verbose: false diff --git a/functions/master/transcribe/latest/static/item.html b/functions/master/transcribe/latest/static/item.html index 7654d6c3..b39372bd 100644 --- a/functions/master/transcribe/latest/static/item.html +++ b/functions/master/transcribe/latest/static/item.html @@ -30,10 +30,8 @@ apiVersion: v1 categories: -- data-preparation +- audio - genai -- huggingface -- machine-learning description: Transcribe audio files into text files doc: '' example: transcribe.ipynb @@ -44,7 +42,7 @@ author: yonatans maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.1 +mlrunVersion: 1.7.0 name: transcribe platformVersion: 3.5.3 spec: @@ -59,7 +57,7 @@ - torch - accelerate url: '' -version: 1.1.0 +version: 1.2.0 diff --git a/functions/master/transcribe/latest/static/transcribe.html b/functions/master/transcribe/latest/static/transcribe.html index 215235bd..7f277c56 100644 --- a/functions/master/transcribe/latest/static/transcribe.html +++ b/functions/master/transcribe/latest/static/transcribe.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/translate/0.2.0/src/function.yaml b/functions/master/translate/0.2.0/src/function.yaml new file mode 100644 index 00000000..9595b77a --- /dev/null +++ b/functions/master/translate/0.2.0/src/function.yaml @@ -0,0 +1,115 @@ +spec: + entry_points: + open_mpi_handler: + lineno: 56 + parameters: + - name: worker_inputs + type: List[str] + - name: root_worker_inputs + type: Dict[str, Any] + default: null + name: open_mpi_handler + has_kwargs: false + doc: '' + has_varargs: false + decorator: + lineno: 68 + parameters: + - name: handler + name: decorator + has_kwargs: false + doc: '' + has_varargs: false + wrapper: + lineno: 73 + name: wrapper + has_kwargs: true + doc: '' + has_varargs: false + translate: + outputs: + - doc: 'A tuple of:' + type: Tuple[str, pd.DataFrame, dict] + lineno: 135 + parameters: + - name: data_path + type: Union[str, List[str], Path] + doc: A directory of text files or a single file or a list of files to translate. + - name: output_directory + type: str + doc: Directory where the translated files will be saved. + - name: model_name + type: str + doc: The name of a model to load. If None, the model name is constructed using + the source and target languages parameters. + default: null + - name: source_language + type: str + doc: The source language code (e.g., 'en' for English). + default: null + - name: target_language + type: str + doc: The target language code (e.g., 'en' for English). + default: null + - name: device + type: str + doc: The device index for transformers. Default will prefer cuda if available. + default: null + - name: model_kwargs + type: dict + doc: Keyword arguments to pass regarding the loading of the model in HuggingFace's + `pipeline` function. + default: null + - name: batch_size + type: int + doc: The number of batches to use in translation. The files are translated + one by one, but the sentences can be batched. + default: 1 + - name: translation_kwargs + type: dict + doc: Additional keyword arguments to pass to a `transformers.TranslationPipeline` + when doing the translation inference. Notice the batch size here is being + added automatically. + default: null + - name: verbose + type: bool + doc: 'Whether to present logs of a progress bar and errors. Default: True.' + default: false + name: translate + has_kwargs: false + doc: 'Translate text files using a transformer model from Huggingface''s hub + according to the source and target languages + + given (or using the directly provided model name). The end result is a directory + of translated text files and a + + dataframe containing the following columns: + + + * text_file - The text file path. + + * translation_file - The translation text file name in the output directory.' + has_varargs: false + build: + requirements: + - transformers + - sentencepiece + - torch + - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK + base_image: mlrun/mlrun + origin_filename: '' + image: '' + default_handler: translate + disable_auto_mount: false + command: '' + description: Translate text files from one language to another +verbose: false +metadata: + categories: + - genai + - NLP + tag: '' + name: translate +kind: job diff --git a/functions/master/translate/0.2.0/src/item.yaml b/functions/master/translate/0.2.0/src/item.yaml new file mode 100644 index 00000000..839d1efa --- /dev/null +++ b/functions/master/translate/0.2.0/src/item.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +categories: +- genai +- NLP +description: Translate text files from one language to another +doc: '' +example: translate.ipynb +generationDate: 2023-12-05:17-20 +hidden: false +icon: '' +labels: + author: guyl +maintainers: [] +marketplaceType: '' +mlrunVersion: 1.7.0 +name: translate +platformVersion: 3.5.3 +spec: + filename: translate.py + handler: translate + image: mlrun/mlrun + kind: job + requirements: + - transformers + - sentencepiece + - torch + - tqdm +url: '' +version: 0.2.0 +test_valid: True diff --git a/functions/master/translate/0.2.0/src/requirements.txt b/functions/master/translate/0.2.0/src/requirements.txt new file mode 100644 index 00000000..94e54846 --- /dev/null +++ b/functions/master/translate/0.2.0/src/requirements.txt @@ -0,0 +1,4 @@ +transformers +tqdm +torch +sentencepiece \ No newline at end of file diff --git a/functions/master/translate/0.2.0/src/test_translate.py b/functions/master/translate/0.2.0/src/test_translate.py new file mode 100644 index 00000000..a22dc899 --- /dev/null +++ b/functions/master/translate/0.2.0/src/test_translate.py @@ -0,0 +1,51 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os.path +import tempfile + +import mlrun + + +def test_translate(): + project = mlrun.new_project("test-translate") + translate_fn = project.set_function("translate.py", "translate", image="mlrun/mlrun") + input_text = "Ali her gece bir kitap okur." + expected_translation = "Ali reads a book every night." + + with tempfile.TemporaryDirectory() as test_dir: + with tempfile.TemporaryDirectory() as data_dir: + with open(os.path.join(data_dir, "test_tr.txt"), "w") as f: + f.write(input_text) + translate_run = translate_fn.run( + handler="translate", + inputs={ + "data_path": data_dir, + }, + params={ + "model_name": "Helsinki-NLP/opus-mt-tr-en", + "device": "cpu", + "output_directory": test_dir, + }, + local=True, + returns=[ + "files: path", + "text_files_dataframe: dataset", + "errors: dict", + ], + artifact_path=test_dir, + ) + assert translate_run.status.state == "completed" + with open(os.path.join(test_dir, "test_tr.txt")) as f: + assert f.read() == expected_translation + diff --git a/functions/master/translate/0.2.0/src/translate.ipynb b/functions/master/translate/0.2.0/src/translate.ipynb new file mode 100644 index 00000000..5e14ee87 --- /dev/null +++ b/functions/master/translate/0.2.0/src/translate.ipynb @@ -0,0 +1,658 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6d3c20aa-7129-4905-beaa-7011943373f5", + "metadata": {}, + "source": [ + "# Translate tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "afe4a3ee-f886-461c-9830-0fd9a5b625c3", + "metadata": {}, + "source": [ + "## Short description and explenation" + ] + }, + { + "cell_type": "markdown", + "id": "313ed5c3-7416-4bbb-a7fb-aa37ab1f8445", + "metadata": {}, + "source": [ + "Machine translation has made huge strides in recent years thanks to advances in deep learning, our translte function makes it even easier to use.
    \n", + "Simply tell it where your file is and the languages you're working with (the one you're translating from and the one you want),
    \n", + "and this function takes care of the rest. It cleverly picks the right pre-trained model for your language pair, ensuring top-notch translations.
    \n", + "\n", + "No need to worry about finding the perfect model or dealing with complex setup – it's all handled behind the scenes.
    \n", + "\n", + "With this function, language translation becomes a breeze, making your documents accessible in any language without breaking a sweat." + ] + }, + { + "cell_type": "markdown", + "id": "9352f799-fe99-4ace-9b44-ca0e28bb1fb4", + "metadata": {}, + "source": [ + "## Background" + ] + }, + { + "cell_type": "markdown", + "id": "6026a8bd-e2e7-454a-b325-9550561a587e", + "metadata": {}, + "source": [ + "The function takes two parameters: a model name or the source and target languages, and a path to one or more text files to translate.\n", + "\n", + "It first checks if a model name was passed. If so, it loads that Helsinki-NLP model.
    \n", + "If not, it looks at the source and target languages and loads the appropriate Helsinki-NLP translation model.\n", + "\n", + "It then reads in the text files and translates them using the loaded model.\n", + "\n", + "Finally, it writes the translated text out to new files and returns the filename or dir name.
    \n", + "\n", + "This allows the user to easily translate a text file to another language using Helsinki-NLP's pre-trained models by just passing the model name or language pair and source text file.
    \n", + "\n", + "This function auto-model selection is based on the great translation models offered by Helsinki. Check them out https://huggingface.co/Helsinki-NLP" + ] + }, + { + "cell_type": "markdown", + "id": "42ec9bc3-2b90-40f1-b10b-5493d9e2b75e", + "metadata": {}, + "source": [ + "## Requirements" + ] + }, + { + "cell_type": "markdown", + "id": "6b756726-e750-4da4-b032-bf5385f85311", + "metadata": {}, + "source": [ + "`transformers`
    \n", + "`tqdm`
    " + ] + }, + { + "cell_type": "markdown", + "id": "212b8161-3e75-459e-98f3-a5b7c5a15efe", + "metadata": {}, + "source": [ + "## Documentation" + ] + }, + { + "cell_type": "markdown", + "id": "9b5fe561-4fbb-4471-91bb-532fa55559f9", + "metadata": {}, + "source": [ + "`data_path`: A directory of text files or a single text file or a list of files to translate.\n", + "\n", + "`output_directory`: Directory where the translated files will be saved.\n", + "\n", + "`model_name`: The name of a model to load. If None, the model name is constructed using the source and
    \n", + " target languages parameters from the \"Helsinki-NLP\" group.\n", + " \n", + "`source_language`: The source language code (e.g., 'en' for English).\n", + "\n", + "`target_language`: The target language code (e.g., 'en' for English).\n", + "\n", + "`model_kwargs`: Keyword arguments to pass regarding the loading of the model in HuggingFace's \"pipeline\"\n", + " function.\n", + " \n", + "`device`: The device index for transformers. Default will prefer cuda if available.\n", + "\n", + "`batch_size`: The number of batches to use in translation. The files are translated one by one, but the\n", + " sentences can be batched.\n", + " \n", + "`translation_kwargs`: Additional keyword arguments to pass to a \"transformers.TranslationPipeline\" when doing
    \n", + " the translation inference. Notice the batch size here is being added automatically.\n" + ] + }, + { + "cell_type": "markdown", + "id": "2e6f44a6-d6ac-48ed-a7d1-936d25e7426c", + "metadata": {}, + "source": [ + "## Demo " + ] + }, + { + "cell_type": "markdown", + "id": "2b231e4c-0224-41a2-87cf-400a4680e2b9", + "metadata": {}, + "source": [ + "The following demo will show an example of translating a text file written in turkish to eanglish using the _tranlate_ function.
    \n", + "\n", + "### (1.) Import the function (import mlrun, set project and import function)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "797ef0d4-f435-485c-b705-e1d6115fb8fd", + "metadata": {}, + "outputs": [], + "source": [ + "import mlrun" + ] + }, + { + "cell_type": "markdown", + "id": "1ff51127-dc54-44d2-bd13-0b81165b2033", + "metadata": {}, + "source": [ + "We want to translate the following turkish sentence into english, so we will write it to a text file." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f9517cc8-a0d6-4169-b746-cf4c265e6a3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing data.txt\n" + ] + } + ], + "source": [ + "%%writefile data.txt\n", + "Ali her gece bir kitap okur. # which means: \"Ali reads a book every night.\"" + ] + }, + { + "cell_type": "markdown", + "id": "c24d71a7-9400-475a-9472-424658801914", + "metadata": {}, + "source": [ + "Setting a project and importing the translate function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e61184ea-44a3-4184-9a2f-9c45b90fdc0f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-06 14:44:05,223 [info] Created and saved project: {'name': 'test-translate', 'from_template': None, 'overwrite': False, 'context': './', 'save': True}\n" + ] + } + ], + "source": [ + "project = mlrun.new_project(\"test-translate\")\n", + "translate_fn = project.set_function(\"hub://translate\", \"translate\")" + ] + }, + { + "cell_type": "markdown", + "id": "558260ce-e453-4e05-a6a7-b2df39cff1b9", + "metadata": {}, + "source": [ + "## Usage" + ] + }, + { + "cell_type": "markdown", + "id": "5a1781ee-a210-4dc1-82de-0f4f5d191173", + "metadata": {}, + "source": [ + "### (2.1.) Manual model selection\n", + "Here we run our function that we've imported from the MLRun Function Hub.
    \n", + "We select the specific model, give the function a path to to the file and output directory and choose to run on the cpu." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9b3107fd-b78d-43de-b4a2-ad3863f72a03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-06 14:48:52,794 [info] Storing function: {'name': 'translate-translate', 'uid': '5768d0ddaf06469da053c85d47f61a47', 'db': 'http://mlrun-api:8080'}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Recommended: pip install sacremoses.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-06 14:48:56,190 [warning] Skipping logging an object with the log hint '{'key': 'errors', 'artifact_type': 'dict'}' due to the following error:\n", + "An exception was raised during the packing of '{}': No packager was found for the combination of 'object_type=builtins.dict' and 'artifact_type=dict'.\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
    \n", + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    test-translate0Dec 06 14:48:52completedtranslate-translate
    v3io_user=yonis
    kind=local
    owner=yonis
    host=jupyter-yonis-7c9bdbfb4d-9g2p2
    data_path
    model_name=Helsinki-NLP/opus-mt-tr-en
    device=cpu
    output_directory=./
    files
    text_files_dataframe
    \n", + "
    \n", + "
    \n", + "
    \n", + " Title\n", + " ×\n", + "
    \n", + " \n", + "
    \n", + "
    \n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + " > to track results use the .show() or .logs() methods or click here to open in UI" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> 2023-12-06 14:48:56,409 [info] Run execution finished: {'status': 'completed', 'name': 'translate-translate'}\n" + ] + } + ], + "source": [ + "translate_run = translate_fn.run(\n", + " handler=\"translate\",\n", + " inputs={\"data_path\": \"data.txt\"},\n", + " params={\n", + " \"model_name\": \"Helsinki-NLP/opus-mt-tr-en\",\n", + " \"device\": \"cpu\",\n", + " \"output_directory\": \"./\",\n", + " },\n", + " local=True,\n", + " returns=[\n", + " \"files: path\",\n", + " \"text_files_dataframe: dataset\",\n", + " \"errors: dict\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8b2fcf2b-3893-4dda-85e2-4a2b9ed0d963", + "metadata": {}, + "source": [ + "### (2.1.) Auto model detectyion" + ] + }, + { + "cell_type": "markdown", + "id": "8c3d24ca-8df7-4204-8b0d-e7a08d53d8c9", + "metadata": {}, + "source": [ + "Here we run our function that we've imported from the MLRun Function Hub.
    \n", + "We select the languages to use for choosing the model, give the function a path to to the file and output directory and choose to run on the cpu." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dbe10afd-5ede-4475-abc2-bb07dfdf33aa", + "metadata": {}, + "outputs": [], + "source": [ + "translate_run = translate_fn.run(\n", + " handler=\"translate\",\n", + " inputs={\"data_path\": \"data.txt\"},\n", + " params={\n", + " \"target_language\": \"en\",\n", + " \"source_language\": \"tr\",\n", + " \"device\": \"cpu\",\n", + " \"output_directory\": \"./\",\n", + " },\n", + " local=True,\n", + " returns=[\n", + " \"files: path\",\n", + " \"text_files_dataframe: dataset\",\n", + " \"errors: dict\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "40e4a666-9680-40d6-93ee-9466d31a9efc", + "metadata": {}, + "source": [ + "We can take alook at the file created" + ] + }, + { + "cell_type": "markdown", + "id": "89a1952c-f3c3-4a7b-bad4-b59c701a5af6", + "metadata": {}, + "source": [ + "### (3.) Review results" + ] + }, + { + "cell_type": "markdown", + "id": "9d583cf9-7e81-4d0d-982f-aba345d4cf9c", + "metadata": {}, + "source": [ + "We can look at the articat returned, the import " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c3dab6f8-6089-46c2-b4b9-899a2442403f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    text_filetranslation_file
    0data.txtdata_2.txt
    \n", + "
    " + ], + "text/plain": [ + " text_file translation_file\n", + "0 data.txt data_2.txt" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "translate_run.artifact(\"text_files_dataframe\").show()" + ] + }, + { + "cell_type": "markdown", + "id": "580a20a2-4877-48b4-8f83-59cbfc2f3b83", + "metadata": {}, + "source": [ + "Checking that translation is correct, we print the text file created by function, and can see the sentence is as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0157bcaf-8f2c-4995-a214-32f2710da4c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Translated text:\n", + "Ali reads a book every night.\n", + "\n" + ] + } + ], + "source": [ + "with open(\"data_2.txt\", \"r\") as f:\n", + " print(f\"Translated text:\\n{f.read()}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mlrun-base", + "language": "python", + "name": "conda-env-mlrun-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/functions/master/translate/0.2.0/src/translate.py b/functions/master/translate/0.2.0/src/translate.py new file mode 100644 index 00000000..360fa620 --- /dev/null +++ b/functions/master/translate/0.2.0/src/translate.py @@ -0,0 +1,396 @@ +# Copyright 2023 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import operator +import pathlib +from functools import reduce, wraps +from typing import Any, Dict, List, Tuple, Union + +import pandas as pd +import transformers +from tqdm import tqdm + +# Get the global logger: +_LOGGER = logging.getLogger() + + +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]: + is_mpi = False + try: + import mlrun + + context = mlrun.get_or_create_ctx(name="mlrun") + is_mpi = context.labels.get("kind", "job") == "mpijob" + + if is_mpi: + try: + from mpi4py import MPI + + return context, MPI.COMM_WORLD + except ModuleNotFoundError as mpi4py_not_found: + context.logger.error( + "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your " + "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi." + ) + raise mpi4py_not_found + else: + return context, None + except ModuleNotFoundError as module_not_found: + if is_mpi: + raise module_not_found + return None, None + + +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, (str, pathlib.Path)): + input_argument = _get_text_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + output_directory = output[0][0] + dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0) + errors_dictionary = reduce( + operator.ior, [err for _, _, err in output], {} + ) + return output_directory, dataframe, errors_dictionary + return None + + return wrapper + + return decorator + + +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def translate( + data_path: Union[str, List[str], pathlib.Path], + output_directory: str, + model_name: str = None, + source_language: str = None, + target_language: str = None, + device: str = None, + model_kwargs: dict = None, + batch_size: int = 1, + translation_kwargs: dict = None, + verbose: bool = False, +) -> Tuple[str, pd.DataFrame, dict]: + """ + Translate text files using a transformer model from Huggingface's hub according to the source and target languages + given (or using the directly provided model name). The end result is a directory of translated text files and a + dataframe containing the following columns: + + * text_file - The text file path. + * translation_file - The translation text file name in the output directory. + + :param data_path: A directory of text files or a single file or a list of files to translate. + :param output_directory: Directory where the translated files will be saved. + :param model_name: The name of a model to load. If None, the model name is constructed using the source and + target languages parameters. + :param source_language: The source language code (e.g., 'en' for English). + :param target_language: The target language code (e.g., 'en' for English). + :param model_kwargs: Keyword arguments to pass regarding the loading of the model in HuggingFace's `pipeline` + function. + :param device: The device index for transformers. Default will prefer cuda if available. + :param batch_size: The number of batches to use in translation. The files are translated one by one, but the + sentences can be batched. + :param translation_kwargs: Additional keyword arguments to pass to a `transformers.TranslationPipeline` when doing + the translation inference. Notice the batch size here is being added automatically. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + :returns: A tuple of: + + * Path to the output directory. + * A dataframe dataset of the translated file names. + * A dictionary of errored files that were not translated. + """ + global _LOGGER + + # Get the input text files to translate: + if verbose: + _LOGGER.info("Collecting text files.") + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + text_files = _get_text_files(data_path=data_path) + else: + text_files = data_path + if verbose: + _LOGGER.info(f"Collected {len(text_files)} text files.") + + # Get the translation pipeline: + if verbose: + _LOGGER.info(f"Loading model - using device '{device}'.") + translation_pipeline, model_name = _get_translation_pipeline( + model_name=model_name, + source_language=source_language, + target_language=target_language, + device=device, + model_kwargs=model_kwargs, + batch_size=batch_size if batch_size != 1 else None, + ) + if verbose: + _LOGGER.info(f"Model '{model_name}' was loaded successfully.") + + # Prepare the successes dataframe and errors dictionary to be returned: + successes = [] + errors = {} + + # Create the output directory: + output_directory = pathlib.Path(output_directory) + output_directory.mkdir(parents=True, exist_ok=True) + + # Prepare the translation keyword arguments: + translation_kwargs = translation_kwargs or {} + + # Go over the audio files and transcribe: + for text_file in tqdm( + text_files, desc="Translating", unit="file", disable=not verbose + ): + try: + # Translate: + translation = _translate( + text_file=text_file, + translation_pipeline=translation_pipeline, + translation_kwargs=translation_kwargs, + ) + # Write the transcription to file: + translation_file = _save_to_file( + translation=translation, + file_name=text_file.stem, + output_directory=output_directory, + ) + # Note as a success in the list: + successes.append( + [ + text_file.name, + translation_file.name, + ] + ) + except Exception as exception: + # Note the exception as error in the dictionary: + if verbose: + _LOGGER.warning(f"Error in file: '{text_file.name}'") + errors[str(text_file.name)] = str(exception) + continue + + # Construct the translations dataframe: + columns = [ + "text_file", + "translation_file", + ] + successes = pd.DataFrame( + successes, + columns=columns, + ) + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(text_files)})\n" + f"Translations summary:\n" + f"{successes.head()}" + ) + return str(output_directory), successes, errors + + +def _get_text_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + text_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + text_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return text_files + + +def _get_translation_pipeline( + model_name: str = None, + source_language: str = None, + target_language: str = None, + device: str = None, + model_kwargs: dict = None, + batch_size: int = None, +) -> Tuple[transformers.Pipeline, str]: + # Construct the model name - if model name is provided (not None) then we take it, otherwise we check both source + # and target were provided to construct the model name: + if model_name is None and (source_language is None or target_language is None): + raise ValueError( + "No model name were given and missing source and / or target languages. In order to translate you must " + "pass a `model_name` or both `source_language` and `target_language`." + ) + elif model_name is None: + model_name = f"Helsinki-NLP/opus-mt-{source_language}-{target_language}" + + # Initialize the translation pipeline: + try: + translation_pipeline = transformers.pipeline( + task="translation", + model=model_name, + tokenizer=model_name, + device=device, + model_kwargs=model_kwargs, + batch_size=batch_size, + ) + except OSError as load_exception: + if ( + "is not a valid model identifier listed on 'https://huggingface.co/models'" + in str(load_exception) + and source_language + ): + raise ValueError( + f"The model '{model_name}' is not a valid model identifier. " + f"The parameters `source_language` and `target_language` are used to construct a Helsinki model for " + f"text to text generation, but the model created from the given languages does not exist. " + f"You may check language identifiers at " + f"https://developers.google.com/admin-sdk/directory/v1/languages, and if the error was not fixed, one " + f"or more language code might be with 3 letters and needs to be found online. " + f"Remember, you can always choose a model directly from the Huggingface hub by using the `model_name` " + f"parameter." + ) from load_exception + raise load_exception + + return translation_pipeline, model_name + + +def _translate( + text_file: pathlib.Path, + translation_pipeline: transformers.Pipeline, + translation_kwargs: dict, +) -> str: + # Read the text from file: + with open(text_file, "r") as fp: + text = fp.read() + + # Split to paragraphs and each paragraph to sentences: + paragraphs = [paragraph.split(".") for paragraph in text.split("\n")] + + # Discover the newline indexes to restore the file to its structure post translation: + newlines_indexes = [] + for paragraph in paragraphs[:-1]: + if len(newlines_indexes) == 0: + newlines_indexes.append(len(paragraph) - 1) + else: + newlines_indexes.append(newlines_indexes[-1] + len(paragraph)) + + # Prepare the batches (each sentence from the paragraphs). Notice we add a dot not only to restore the sentence + # structure but to ignore empty strings as it will ruin the translation: + sentences = [f"{line}." for paragraph in paragraphs for line in paragraph] + + # Translate the sentences: + translations = translation_pipeline(sentences, **translation_kwargs) + + # Restructure the full text from the sentences: + translated_text = [] + newline_index = newlines_indexes.pop(0) if newlines_indexes else None + for i, translation in enumerate(translations): + # Get the translation: + text = translation["translation_text"] + # Validate if it was an empty sentence before: + if text == ".": + text = "" + # Check if needed to insert a newline: + if newline_index and newline_index == i: + text += "\n" + newline_index = newlines_indexes.pop(0) if newlines_indexes else None + # Collect it: + translated_text.append(text) + translated_text = "".join(translated_text) + + return translated_text + + +def _save_to_file( + translation: str, file_name: str, output_directory: pathlib.Path +) -> pathlib.Path: + # Prepare the file full path (checking for no duplications): + translation_file = output_directory / f"{file_name}.txt" + i = 1 + while translation_file.exists(): + i += 1 + translation_file = output_directory / f"{file_name}_{i}.txt" + + # Make sure all directories are created: + translation_file.parent.mkdir(exist_ok=True, parents=True) + + # Write to file: + with open(translation_file, "w") as fp: + fp.write(translation) + + return translation_file diff --git a/functions/master/translate/0.2.0/static/documentation.html b/functions/master/translate/0.2.0/static/documentation.html new file mode 100644 index 00000000..9e4fdd01 --- /dev/null +++ b/functions/master/translate/0.2.0/static/documentation.html @@ -0,0 +1,282 @@ + + + + + + + +translate package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    translate package

    + +
    + +
    +
    + +
    +
    +

    translate package#

    +
    +

    Submodules#

    +
    +
    +

    translate.translate module#

    +
    +
    +translate.translate.open_mpi_handler(worker_inputs: List[str], root_worker_inputs: Dict[str, Any] | None = None)[source]#
    +
    +
    +
    +translate.translate.translate(data_path: str | List[str] | Path, output_directory: str, model_name: str | None = None, source_language: str | None = None, target_language: str | None = None, device: str | None = None, model_kwargs: dict | None = None, batch_size: int = 1, translation_kwargs: dict | None = None, verbose: bool = False) Tuple[str, DataFrame, dict][source]#
    +

    Translate text files using a transformer model from Huggingface’s hub according to the source and target languages +given (or using the directly provided model name). The end result is a directory of translated text files and a +dataframe containing the following columns:

    +
      +
    • text_file - The text file path.

    • +
    • translation_file - The translation text file name in the output directory.

    • +
    +
    +
    Parameters:
    +
      +
    • data_path – A directory of text files or a single file or a list of files to translate.

    • +
    • output_directory – Directory where the translated files will be saved.

    • +
    • model_name – The name of a model to load. If None, the model name is constructed using the source and +target languages parameters.

    • +
    • source_language – The source language code (e.g., ‘en’ for English).

    • +
    • target_language – The target language code (e.g., ‘en’ for English).

    • +
    • model_kwargs – Keyword arguments to pass regarding the loading of the model in HuggingFace’s pipeline +function.

    • +
    • device – The device index for transformers. Default will prefer cuda if available.

    • +
    • batch_size – The number of batches to use in translation. The files are translated one by one, but the +sentences can be batched.

    • +
    • translation_kwargs – Additional keyword arguments to pass to a transformers.TranslationPipeline when doing +the translation inference. Notice the batch size here is being added automatically.

    • +
    • verbose – Whether to present logs of a progress bar and errors. Default: True.

    • +
    +
    +
    Returns:
    +

    A tuple of:

    +
      +
    • Path to the output directory.

    • +
    • A dataframe dataset of the translated file names.

    • +
    • A dictionary of errored files that were not translated.

    • +
    +

    +
    +
    +
    +
    +
    +

    Module contents#

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/translate/0.2.0/static/example.html b/functions/master/translate/0.2.0/static/example.html new file mode 100644 index 00000000..6b086df4 --- /dev/null +++ b/functions/master/translate/0.2.0/static/example.html @@ -0,0 +1,647 @@ + + + + + + + +Translate tutorial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + +
    +
    +

    Translate tutorial#

    +
    +

    Short description and explenation#

    +

    Machine translation has made huge strides in recent years thanks to advances in deep learning, our translte function makes it even easier to use.
    +Simply tell it where your file is and the languages you’re working with (the one you’re translating from and the one you want),
    +and this function takes care of the rest. It cleverly picks the right pre-trained model for your language pair, ensuring top-notch translations.

    +

    No need to worry about finding the perfect model or dealing with complex setup – it’s all handled behind the scenes.

    +

    With this function, language translation becomes a breeze, making your documents accessible in any language without breaking a sweat.

    +
    +
    +

    Background#

    +

    The function takes two parameters: a model name or the source and target languages, and a path to one or more text files to translate.

    +

    It first checks if a model name was passed. If so, it loads that Helsinki-NLP model.
    +If not, it looks at the source and target languages and loads the appropriate Helsinki-NLP translation model.

    +

    It then reads in the text files and translates them using the loaded model.

    +

    Finally, it writes the translated text out to new files and returns the filename or dir name.

    +

    This allows the user to easily translate a text file to another language using Helsinki-NLP’s pre-trained models by just passing the model name or language pair and source text file.

    +

    This function auto-model selection is based on the great translation models offered by Helsinki. Check them out https://huggingface.co/Helsinki-NLP

    +
    +
    +

    Requirements#

    +

    transformers
    +tqdm

    +
    +
    +

    Documentation#

    +

    data_path: A directory of text files or a single text file or a list of files to translate.

    +

    output_directory: Directory where the translated files will be saved.

    +

    model_name: The name of a model to load. If None, the model name is constructed using the source and
    +target languages parameters from the “Helsinki-NLP” group.

    +

    source_language: The source language code (e.g., ‘en’ for English).

    +

    target_language: The target language code (e.g., ‘en’ for English).

    +

    model_kwargs: Keyword arguments to pass regarding the loading of the model in HuggingFace’s “pipeline” +function.

    +

    device: The device index for transformers. Default will prefer cuda if available.

    +

    batch_size: The number of batches to use in translation. The files are translated one by one, but the +sentences can be batched.

    +

    translation_kwargs: Additional keyword arguments to pass to a “transformers.TranslationPipeline” when doing
    +the translation inference. Notice the batch size here is being added automatically.

    +
    +
    +

    Demo#

    +

    The following demo will show an example of translating a text file written in turkish to eanglish using the tranlate function.

    +
    +

    (1.) Import the function (import mlrun, set project and import function)#

    +
    +
    +
    import mlrun
    +
    +
    +
    +
    +

    We want to translate the following turkish sentence into english, so we will write it to a text file.

    +
    +
    +
    %%writefile data.txt
    +Ali her gece bir kitap okur. # which means: "Ali reads a book every night."
    +
    +
    +
    +
    +
    Writing data.txt
    +
    +
    +
    +
    +

    Setting a project and importing the translate function

    +
    +
    +
    project = mlrun.new_project("test-translate")
    +translate_fn = project.set_function("hub://translate", "translate")
    +
    +
    +
    +
    +
    > 2023-12-06 14:44:05,223 [info] Created and saved project: {'name': 'test-translate', 'from_template': None, 'overwrite': False, 'context': './', 'save': True}
    +
    +
    +
    +
    +
    +
    +
    +

    Usage#

    +
    +

    (2.1.) Manual model selection#

    +

    Here we run our function that we’ve imported from the MLRun Function Hub.
    +We select the specific model, give the function a path to to the file and output directory and choose to run on the cpu.

    +
    +
    +
    translate_run = translate_fn.run(
    +    handler="translate",
    +    inputs={"data_path": "data.txt"},
    +    params={
    +        "model_name": "Helsinki-NLP/opus-mt-tr-en",
    +        "device": "cpu",
    +        "output_directory": "./",
    +    },
    +    local=True,
    +    returns=[
    +        "files: path",
    +        "text_files_dataframe: dataset",
    +        "errors: dict",
    +    ],
    +)
    +
    +
    +
    +
    +
    > 2023-12-06 14:48:52,794 [info] Storing function: {'name': 'translate-translate', 'uid': '5768d0ddaf06469da053c85d47f61a47', 'db': 'http://mlrun-api:8080'}
    +
    +
    +
    Recommended: pip install sacremoses.
    +
    +
    +
    > 2023-12-06 14:48:56,190 [warning] Skipping logging an object with the log hint '{'key': 'errors', 'artifact_type': 'dict'}' due to the following error:
    +An exception was raised during the packing of '{}': No packager was found for the combination of 'object_type=builtins.dict' and 'artifact_type=dict'.
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    projectuiditerstartstatenamelabelsinputsparametersresultsartifacts
    test-translate0Dec 06 14:48:52completedtranslate-translate
    v3io_user=yonis
    kind=local
    owner=yonis
    host=jupyter-yonis-7c9bdbfb4d-9g2p2
    data_path
    model_name=Helsinki-NLP/opus-mt-tr-en
    device=cpu
    output_directory=./
    files
    text_files_dataframe
    +
    + +
    +
    
    +
    +
    +
    > to track results use the .show() or .logs() methods or click here to open in UI
    > 2023-12-06 14:48:56,409 [info] Run execution finished: {'status': 'completed', 'name': 'translate-translate'}
    +
    +
    +
    +
    +
    +
    +

    (2.1.) Auto model detectyion#

    +

    Here we run our function that we’ve imported from the MLRun Function Hub.
    +We select the languages to use for choosing the model, give the function a path to to the file and output directory and choose to run on the cpu.

    +
    +
    +
    translate_run = translate_fn.run(
    +    handler="translate",
    +    inputs={"data_path": "data.txt"},
    +    params={
    +        "target_language": "en",
    +        "source_language": "tr",
    +        "device": "cpu",
    +        "output_directory": "./",
    +    },
    +    local=True,
    +    returns=[
    +        "files: path",
    +        "text_files_dataframe: dataset",
    +        "errors: dict",
    +    ],
    +)
    +
    +
    +
    +
    +

    We can take alook at the file created

    +
    +
    +

    (3.) Review results#

    +

    We can look at the articat returned, the import

    +
    +
    +
    translate_run.artifact("text_files_dataframe").show()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + +
    text_filetranslation_file
    0data.txtdata_2.txt
    +
    +
    +

    Checking that translation is correct, we print the text file created by function, and can see the sentence is as expected.

    +
    +
    +
    with open("data_2.txt", "r") as f:
    +    print(f"Translated text:\n{f.read()}")
    +
    +
    +
    +
    +
    Translated text:
    +Ali reads a book every night.
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/translate/0.2.0/static/function.html b/functions/master/translate/0.2.0/static/function.html new file mode 100644 index 00000000..38951174 --- /dev/null +++ b/functions/master/translate/0.2.0/static/function.html @@ -0,0 +1,150 @@ + + + + + + + + + + + Source + + + + +
    +        
    +spec:
    +  entry_points:
    +    open_mpi_handler:
    +      lineno: 56
    +      parameters:
    +      - name: worker_inputs
    +        type: List[str]
    +      - name: root_worker_inputs
    +        type: Dict[str, Any]
    +        default: null
    +      name: open_mpi_handler
    +      has_kwargs: false
    +      doc: ''
    +      has_varargs: false
    +    decorator:
    +      lineno: 68
    +      parameters:
    +      - name: handler
    +      name: decorator
    +      has_kwargs: false
    +      doc: ''
    +      has_varargs: false
    +    wrapper:
    +      lineno: 73
    +      name: wrapper
    +      has_kwargs: true
    +      doc: ''
    +      has_varargs: false
    +    translate:
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[str, pd.DataFrame, dict]
    +      lineno: 135
    +      parameters:
    +      - name: data_path
    +        type: Union[str, List[str], Path]
    +        doc: A directory of text files or a single file or a list of files to translate.
    +      - name: output_directory
    +        type: str
    +        doc: Directory where the translated files will be saved.
    +      - name: model_name
    +        type: str
    +        doc: The name of a model to load. If None, the model name is constructed using
    +          the source and target languages parameters.
    +        default: null
    +      - name: source_language
    +        type: str
    +        doc: The source language code (e.g., 'en' for English).
    +        default: null
    +      - name: target_language
    +        type: str
    +        doc: The target language code (e.g., 'en' for English).
    +        default: null
    +      - name: device
    +        type: str
    +        doc: The device index for transformers. Default will prefer cuda if available.
    +        default: null
    +      - name: model_kwargs
    +        type: dict
    +        doc: Keyword arguments to pass regarding the loading of the model in HuggingFace's
    +          `pipeline` function.
    +        default: null
    +      - name: batch_size
    +        type: int
    +        doc: The number of batches to use in translation. The files are translated
    +          one by one, but the sentences can be batched.
    +        default: 1
    +      - name: translation_kwargs
    +        type: dict
    +        doc: Additional keyword arguments to pass to a `transformers.TranslationPipeline`
    +          when doing the translation inference. Notice the batch size here is being
    +          added automatically.
    +        default: null
    +      - name: verbose
    +        type: bool
    +        doc: 'Whether to present logs of a progress bar and errors. Default: True.'
    +        default: false
    +      name: translate
    +      has_kwargs: false
    +      doc: 'Translate text files using a transformer model from Huggingface''s hub
    +        according to the source and target languages
    +
    +        given (or using the directly provided model name). The end result is a directory
    +        of translated text files and a
    +
    +        dataframe containing the following columns:
    +
    +
    +        * text_file - The text file path.
    +
    +        * translation_file - The translation text file name in the output directory.'
    +      has_varargs: false
    +  build:
    +    requirements:
    +    - transformers
    +    - sentencepiece
    +    - torch
    +    - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK
    +    base_image: mlrun/mlrun
    +    origin_filename: ''
    +  image: ''
    +  default_handler: translate
    +  disable_auto_mount: false
    +  command: ''
    +  description: Translate text files from one language to another
    +verbose: false
    +metadata:
    +  categories:
    +  - genai
    +  - NLP
    +  tag: ''
    +  name: translate
    +kind: job
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/translate/0.2.0/static/item.html b/functions/master/translate/0.2.0/static/item.html new file mode 100644 index 00000000..0c48dfc6 --- /dev/null +++ b/functions/master/translate/0.2.0/static/item.html @@ -0,0 +1,65 @@ + + + + + + + + + + + Source + + + + +
    +        
    +apiVersion: v1
    +categories:
    +- genai
    +- NLP
    +description: Translate text files from one language to another
    +doc: ''
    +example: translate.ipynb
    +generationDate: 2023-12-05:17-20
    +hidden: false
    +icon: ''
    +labels:
    +  author: guyl
    +maintainers: []
    +marketplaceType: ''
    +mlrunVersion: 1.7.0
    +name: translate
    +platformVersion: 3.5.3
    +spec:
    +  filename: translate.py
    +  handler: translate
    +  image: mlrun/mlrun
    +  kind: job
    +  requirements:
    +    - transformers
    +    - sentencepiece
    +    - torch
    +    - tqdm
    +url: ''
    +version: 0.2.0
    +test_valid: True
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/translate/0.2.0/static/source.html b/functions/master/translate/0.2.0/static/source.html new file mode 100644 index 00000000..1582a69b --- /dev/null +++ b/functions/master/translate/0.2.0/static/source.html @@ -0,0 +1,431 @@ + + + + + + + + + + + Source + + + + +
    +        
    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import logging
    +import operator
    +import pathlib
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import transformers
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +        else:
    +            return context, None
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +def open_mpi_handler(
    +    worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None
    +):
    +    global _LOGGER
    +
    +    # Check for MLRun and OpenMPI availability:
    +    context, comm = _check_mlrun_and_open_mpi()
    +
    +    # Check if MLRun is available, set the global logger to MLRun's:
    +    if context:
    +        _LOGGER = context.logger
    +
    +    def decorator(handler):
    +        if comm is None or comm.Get_size() == 1:
    +            return handler
    +
    +        @wraps(handler)
    +        def wrapper(**kwargs):
    +            # Get the open mpi environment properties:
    +            size = comm.Get_size()
    +            rank = comm.Get_rank()
    +
    +            # Give the correct chunk of the workers inputs:
    +            for worker_input in worker_inputs:
    +                input_argument = kwargs[worker_input]
    +                if input_argument is None:
    +                    continue
    +                if isinstance(input_argument, (str, pathlib.Path)):
    +                    input_argument = _get_text_files(
    +                        data_path=pathlib.Path(input_argument).absolute()
    +                    )
    +                if len(input_argument) < size:
    +                    raise ValueError(
    +                        f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. "
    +                        f"Please reduce the amount of workers for this input."
    +                    )
    +                even_chunk_size = len(input_argument) // size
    +                chunk_start = rank * even_chunk_size
    +                chunk_end = (
    +                    (rank + 1) * even_chunk_size
    +                    if rank + 1 < size
    +                    else len(input_argument)
    +                )
    +                context.logger.info(
    +                    f"Rank #{rank}: Processing input chunk of '{worker_input}' "
    +                    f"from index {chunk_start} to {chunk_end}."
    +                )
    +                if isinstance(input_argument, list):
    +                    input_argument = input_argument[chunk_start:chunk_end]
    +                elif isinstance(input_argument, pd.DataFrame):
    +                    input_argument = input_argument.iloc[chunk_start:chunk_end:, :]
    +                kwargs[worker_input] = input_argument
    +
    +            # Set the root worker only arguments:
    +            if rank == 0 and root_worker_inputs:
    +                kwargs.update(root_worker_inputs)
    +
    +            # Run the worker:
    +            output = handler(**kwargs)
    +
    +            # Send the output to the root rank (rank #0):
    +            output = comm.gather(output, root=0)
    +            if rank == 0:
    +                # Join the outputs:
    +                context.logger.info("Collecting data from workers to root worker.")
    +                output_directory = output[0][0]
    +                dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0)
    +                errors_dictionary = reduce(
    +                    operator.ior, [err for _, _, err in output], {}
    +                )
    +                return output_directory, dataframe, errors_dictionary
    +            return None
    +
    +        return wrapper
    +
    +    return decorator
    +
    +
    +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True})
    +def translate(
    +    data_path: Union[str, List[str], pathlib.Path],
    +    output_directory: str,
    +    model_name: str = None,
    +    source_language: str = None,
    +    target_language: str = None,
    +    device: str = None,
    +    model_kwargs: dict = None,
    +    batch_size: int = 1,
    +    translation_kwargs: dict = None,
    +    verbose: bool = False,
    +) -> Tuple[str, pd.DataFrame, dict]:
    +    """
    +    Translate text files using a transformer model from Huggingface's hub according to the source and target languages
    +    given (or using the directly provided model name). The end result is a directory of translated text files and a
    +    dataframe containing the following columns:
    +
    +    * text_file - The text file path.
    +    * translation_file - The translation text file name in the output directory.
    +
    +    :param data_path:          A directory of text files or a single file or a list of files to translate.
    +    :param output_directory:   Directory where the translated files will be saved.
    +    :param model_name:         The name of a model to load. If None, the model name is constructed using the source and
    +                               target languages parameters.
    +    :param source_language:    The source language code (e.g., 'en' for English).
    +    :param target_language:    The target language code (e.g., 'en' for English).
    +    :param model_kwargs:       Keyword arguments to pass regarding the loading of the model in HuggingFace's `pipeline`
    +                               function.
    +    :param device:             The device index for transformers. Default will prefer cuda if available.
    +    :param batch_size:         The number of batches to use in translation. The files are translated one by one, but the
    +                               sentences can be batched.
    +    :param translation_kwargs: Additional keyword arguments to pass to a `transformers.TranslationPipeline` when doing
    +                               the translation inference. Notice the batch size here is being added automatically.
    +    :param verbose:            Whether to present logs of a progress bar and errors. Default: True.
    +
    +    :returns: A tuple of:
    +
    +              * Path to the output directory.
    +              * A dataframe dataset of the translated file names.
    +              * A dictionary of errored files that were not translated.
    +    """
    +    global _LOGGER
    +
    +    # Get the input text files to translate:
    +    if verbose:
    +        _LOGGER.info("Collecting text files.")
    +    if isinstance(data_path, str):
    +        data_path = pathlib.Path(data_path).absolute()
    +        text_files = _get_text_files(data_path=data_path)
    +    else:
    +        text_files = data_path
    +    if verbose:
    +        _LOGGER.info(f"Collected {len(text_files)} text files.")
    +
    +    # Get the translation pipeline:
    +    if verbose:
    +        _LOGGER.info(f"Loading model - using device '{device}'.")
    +    translation_pipeline, model_name = _get_translation_pipeline(
    +        model_name=model_name,
    +        source_language=source_language,
    +        target_language=target_language,
    +        device=device,
    +        model_kwargs=model_kwargs,
    +        batch_size=batch_size if batch_size != 1 else None,
    +    )
    +    if verbose:
    +        _LOGGER.info(f"Model '{model_name}' was loaded successfully.")
    +
    +    # Prepare the successes dataframe and errors dictionary to be returned:
    +    successes = []
    +    errors = {}
    +
    +    # Create the output directory:
    +    output_directory = pathlib.Path(output_directory)
    +    output_directory.mkdir(parents=True, exist_ok=True)
    +
    +    # Prepare the translation keyword arguments:
    +    translation_kwargs = translation_kwargs or {}
    +
    +    # Go over the audio files and transcribe:
    +    for text_file in tqdm(
    +        text_files, desc="Translating", unit="file", disable=not verbose
    +    ):
    +        try:
    +            # Translate:
    +            translation = _translate(
    +                text_file=text_file,
    +                translation_pipeline=translation_pipeline,
    +                translation_kwargs=translation_kwargs,
    +            )
    +            # Write the transcription to file:
    +            translation_file = _save_to_file(
    +                translation=translation,
    +                file_name=text_file.stem,
    +                output_directory=output_directory,
    +            )
    +            # Note as a success in the list:
    +            successes.append(
    +                [
    +                    text_file.name,
    +                    translation_file.name,
    +                ]
    +            )
    +        except Exception as exception:
    +            # Note the exception as error in the dictionary:
    +            if verbose:
    +                _LOGGER.warning(f"Error in file: '{text_file.name}'")
    +            errors[str(text_file.name)] = str(exception)
    +            continue
    +
    +    # Construct the translations dataframe:
    +    columns = [
    +        "text_file",
    +        "translation_file",
    +    ]
    +    successes = pd.DataFrame(
    +        successes,
    +        columns=columns,
    +    )
    +
    +    # Print the head of the produced dataframe and return:
    +    if verbose:
    +        _LOGGER.info(
    +            f"Done ({successes.shape[0]}/{len(text_files)})\n"
    +            f"Translations summary:\n"
    +            f"{successes.head()}"
    +        )
    +    return str(output_directory), successes, errors
    +
    +
    +def _get_text_files(
    +    data_path: pathlib.Path,
    +) -> List[pathlib.Path]:
    +    # Check if the path is of a directory or a file:
    +    if data_path.is_dir():
    +        # Get all files inside the directory:
    +        text_files = list(data_path.glob("*.*"))
    +    elif data_path.is_file():
    +        text_files = [data_path]
    +    else:
    +        raise ValueError(
    +            f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. "
    +            f"Given: {str(data_path)} "
    +        )
    +
    +    return text_files
    +
    +
    +def _get_translation_pipeline(
    +    model_name: str = None,
    +    source_language: str = None,
    +    target_language: str = None,
    +    device: str = None,
    +    model_kwargs: dict = None,
    +    batch_size: int = None,
    +) -> Tuple[transformers.Pipeline, str]:
    +    # Construct the model name - if model name is provided (not None) then we take it, otherwise we check both source
    +    # and target were provided to construct the model name:
    +    if model_name is None and (source_language is None or target_language is None):
    +        raise ValueError(
    +            "No model name were given and missing source and / or target languages. In order to translate you must "
    +            "pass a `model_name` or both `source_language` and `target_language`."
    +        )
    +    elif model_name is None:
    +        model_name = f"Helsinki-NLP/opus-mt-{source_language}-{target_language}"
    +
    +    # Initialize the translation pipeline:
    +    try:
    +        translation_pipeline = transformers.pipeline(
    +            task="translation",
    +            model=model_name,
    +            tokenizer=model_name,
    +            device=device,
    +            model_kwargs=model_kwargs,
    +            batch_size=batch_size,
    +        )
    +    except OSError as load_exception:
    +        if (
    +            "is not a valid model identifier listed on 'https://huggingface.co/models'"
    +            in str(load_exception)
    +            and source_language
    +        ):
    +            raise ValueError(
    +                f"The model '{model_name}' is not a valid model identifier. "
    +                f"The parameters `source_language` and `target_language` are used to construct a Helsinki model for "
    +                f"text to text generation, but the model created from the given languages does not exist. "
    +                f"You may check language identifiers at "
    +                f"https://developers.google.com/admin-sdk/directory/v1/languages, and if the error was not fixed, one "
    +                f"or more language code might be with 3 letters and needs to be found online. "
    +                f"Remember, you can always choose a model directly from the Huggingface hub by using the `model_name` "
    +                f"parameter."
    +            ) from load_exception
    +        raise load_exception
    +
    +    return translation_pipeline, model_name
    +
    +
    +def _translate(
    +    text_file: pathlib.Path,
    +    translation_pipeline: transformers.Pipeline,
    +    translation_kwargs: dict,
    +) -> str:
    +    # Read the text from file:
    +    with open(text_file, "r") as fp:
    +        text = fp.read()
    +
    +    # Split to paragraphs and each paragraph to sentences:
    +    paragraphs = [paragraph.split(".") for paragraph in text.split("\n")]
    +
    +    # Discover the newline indexes to restore the file to its structure post translation:
    +    newlines_indexes = []
    +    for paragraph in paragraphs[:-1]:
    +        if len(newlines_indexes) == 0:
    +            newlines_indexes.append(len(paragraph) - 1)
    +        else:
    +            newlines_indexes.append(newlines_indexes[-1] + len(paragraph))
    +
    +    # Prepare the batches (each sentence from the paragraphs). Notice we add a dot not only to restore the sentence
    +    # structure but to ignore empty strings as it will ruin the translation:
    +    sentences = [f"{line}." for paragraph in paragraphs for line in paragraph]
    +
    +    # Translate the sentences:
    +    translations = translation_pipeline(sentences, **translation_kwargs)
    +
    +    # Restructure the full text from the sentences:
    +    translated_text = []
    +    newline_index = newlines_indexes.pop(0) if newlines_indexes else None
    +    for i, translation in enumerate(translations):
    +        # Get the translation:
    +        text = translation["translation_text"]
    +        # Validate if it was an empty sentence before:
    +        if text == ".":
    +            text = ""
    +        # Check if needed to insert a newline:
    +        if newline_index and newline_index == i:
    +            text += "\n"
    +            newline_index = newlines_indexes.pop(0) if newlines_indexes else None
    +        # Collect it:
    +        translated_text.append(text)
    +    translated_text = "".join(translated_text)
    +
    +    return translated_text
    +
    +
    +def _save_to_file(
    +    translation: str, file_name: str, output_directory: pathlib.Path
    +) -> pathlib.Path:
    +    # Prepare the file full path (checking for no duplications):
    +    translation_file = output_directory / f"{file_name}.txt"
    +    i = 1
    +    while translation_file.exists():
    +        i += 1
    +        translation_file = output_directory / f"{file_name}_{i}.txt"
    +
    +    # Make sure all directories are created:
    +    translation_file.parent.mkdir(exist_ok=True, parents=True)
    +
    +    # Write to file:
    +    with open(translation_file, "w") as fp:
    +        fp.write(translation)
    +
    +    return translation_file
    +
    +        
    +    
    + + \ No newline at end of file diff --git a/functions/master/translate/0.2.0/static/translate.html b/functions/master/translate/0.2.0/static/translate.html new file mode 100644 index 00000000..153b48a4 --- /dev/null +++ b/functions/master/translate/0.2.0/static/translate.html @@ -0,0 +1,574 @@ + + + + + + + +translate.translate + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    + +
    +
    +
    +
    +
    + +
    +

    Source code for translate.translate

    +# Copyright 2023 Iguazio
    +#
    +# Licensed under the Apache License, Version 2.0 (the "License");
    +# you may not use this file except in compliance with the License.
    +# You may obtain a copy of the License at
    +#
    +#   http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import logging
    +import operator
    +import pathlib
    +from functools import reduce, wraps
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import pandas as pd
    +import transformers
    +from tqdm import tqdm
    +
    +# Get the global logger:
    +_LOGGER = logging.getLogger()
    +
    +
    +def _check_mlrun_and_open_mpi() -> Tuple["mlrun.MLClientCtx", "mpi4py.MPI.Intracomm"]:
    +    is_mpi = False
    +    try:
    +        import mlrun
    +
    +        context = mlrun.get_or_create_ctx(name="mlrun")
    +        is_mpi = context.labels.get("kind", "job") == "mpijob"
    +
    +        if is_mpi:
    +            try:
    +                from mpi4py import MPI
    +
    +                return context, MPI.COMM_WORLD
    +            except ModuleNotFoundError as mpi4py_not_found:
    +                context.logger.error(
    +                    "To distribute the function using MLRun's 'mpijob' you need to have `mpi4py` package in your "
    +                    "interpreter. Please run `pip install mpi4py` and make sure you have open-mpi."
    +                )
    +                raise mpi4py_not_found
    +        else:
    +            return context, None
    +    except ModuleNotFoundError as module_not_found:
    +        if is_mpi:
    +            raise module_not_found
    +    return None, None
    +
    +
    +
    +[docs] +def open_mpi_handler( + worker_inputs: List[str], root_worker_inputs: Dict[str, Any] = None +): + global _LOGGER + + # Check for MLRun and OpenMPI availability: + context, comm = _check_mlrun_and_open_mpi() + + # Check if MLRun is available, set the global logger to MLRun's: + if context: + _LOGGER = context.logger + + def decorator(handler): + if comm is None or comm.Get_size() == 1: + return handler + + @wraps(handler) + def wrapper(**kwargs): + # Get the open mpi environment properties: + size = comm.Get_size() + rank = comm.Get_rank() + + # Give the correct chunk of the workers inputs: + for worker_input in worker_inputs: + input_argument = kwargs[worker_input] + if input_argument is None: + continue + if isinstance(input_argument, (str, pathlib.Path)): + input_argument = _get_text_files( + data_path=pathlib.Path(input_argument).absolute() + ) + if len(input_argument) < size: + raise ValueError( + f"Cannot split the input '{worker_input}' of length {len(input_argument)} to {size} workers. " + f"Please reduce the amount of workers for this input." + ) + even_chunk_size = len(input_argument) // size + chunk_start = rank * even_chunk_size + chunk_end = ( + (rank + 1) * even_chunk_size + if rank + 1 < size + else len(input_argument) + ) + context.logger.info( + f"Rank #{rank}: Processing input chunk of '{worker_input}' " + f"from index {chunk_start} to {chunk_end}." + ) + if isinstance(input_argument, list): + input_argument = input_argument[chunk_start:chunk_end] + elif isinstance(input_argument, pd.DataFrame): + input_argument = input_argument.iloc[chunk_start:chunk_end:, :] + kwargs[worker_input] = input_argument + + # Set the root worker only arguments: + if rank == 0 and root_worker_inputs: + kwargs.update(root_worker_inputs) + + # Run the worker: + output = handler(**kwargs) + + # Send the output to the root rank (rank #0): + output = comm.gather(output, root=0) + if rank == 0: + # Join the outputs: + context.logger.info("Collecting data from workers to root worker.") + output_directory = output[0][0] + dataframe = pd.concat(objs=[df for _, df, _ in output], axis=0) + errors_dictionary = reduce( + operator.ior, [err for _, _, err in output], {} + ) + return output_directory, dataframe, errors_dictionary + return None + + return wrapper + + return decorator
    + + + +
    +[docs] +@open_mpi_handler(worker_inputs=["data_path"], root_worker_inputs={"verbose": True}) +def translate( + data_path: Union[str, List[str], pathlib.Path], + output_directory: str, + model_name: str = None, + source_language: str = None, + target_language: str = None, + device: str = None, + model_kwargs: dict = None, + batch_size: int = 1, + translation_kwargs: dict = None, + verbose: bool = False, +) -> Tuple[str, pd.DataFrame, dict]: + """ + Translate text files using a transformer model from Huggingface's hub according to the source and target languages + given (or using the directly provided model name). The end result is a directory of translated text files and a + dataframe containing the following columns: + + * text_file - The text file path. + * translation_file - The translation text file name in the output directory. + + :param data_path: A directory of text files or a single file or a list of files to translate. + :param output_directory: Directory where the translated files will be saved. + :param model_name: The name of a model to load. If None, the model name is constructed using the source and + target languages parameters. + :param source_language: The source language code (e.g., 'en' for English). + :param target_language: The target language code (e.g., 'en' for English). + :param model_kwargs: Keyword arguments to pass regarding the loading of the model in HuggingFace's `pipeline` + function. + :param device: The device index for transformers. Default will prefer cuda if available. + :param batch_size: The number of batches to use in translation. The files are translated one by one, but the + sentences can be batched. + :param translation_kwargs: Additional keyword arguments to pass to a `transformers.TranslationPipeline` when doing + the translation inference. Notice the batch size here is being added automatically. + :param verbose: Whether to present logs of a progress bar and errors. Default: True. + + :returns: A tuple of: + + * Path to the output directory. + * A dataframe dataset of the translated file names. + * A dictionary of errored files that were not translated. + """ + global _LOGGER + + # Get the input text files to translate: + if verbose: + _LOGGER.info("Collecting text files.") + if isinstance(data_path, str): + data_path = pathlib.Path(data_path).absolute() + text_files = _get_text_files(data_path=data_path) + else: + text_files = data_path + if verbose: + _LOGGER.info(f"Collected {len(text_files)} text files.") + + # Get the translation pipeline: + if verbose: + _LOGGER.info(f"Loading model - using device '{device}'.") + translation_pipeline, model_name = _get_translation_pipeline( + model_name=model_name, + source_language=source_language, + target_language=target_language, + device=device, + model_kwargs=model_kwargs, + batch_size=batch_size if batch_size != 1 else None, + ) + if verbose: + _LOGGER.info(f"Model '{model_name}' was loaded successfully.") + + # Prepare the successes dataframe and errors dictionary to be returned: + successes = [] + errors = {} + + # Create the output directory: + output_directory = pathlib.Path(output_directory) + output_directory.mkdir(parents=True, exist_ok=True) + + # Prepare the translation keyword arguments: + translation_kwargs = translation_kwargs or {} + + # Go over the audio files and transcribe: + for text_file in tqdm( + text_files, desc="Translating", unit="file", disable=not verbose + ): + try: + # Translate: + translation = _translate( + text_file=text_file, + translation_pipeline=translation_pipeline, + translation_kwargs=translation_kwargs, + ) + # Write the transcription to file: + translation_file = _save_to_file( + translation=translation, + file_name=text_file.stem, + output_directory=output_directory, + ) + # Note as a success in the list: + successes.append( + [ + text_file.name, + translation_file.name, + ] + ) + except Exception as exception: + # Note the exception as error in the dictionary: + if verbose: + _LOGGER.warning(f"Error in file: '{text_file.name}'") + errors[str(text_file.name)] = str(exception) + continue + + # Construct the translations dataframe: + columns = [ + "text_file", + "translation_file", + ] + successes = pd.DataFrame( + successes, + columns=columns, + ) + + # Print the head of the produced dataframe and return: + if verbose: + _LOGGER.info( + f"Done ({successes.shape[0]}/{len(text_files)})\n" + f"Translations summary:\n" + f"{successes.head()}" + ) + return str(output_directory), successes, errors
    + + + +def _get_text_files( + data_path: pathlib.Path, +) -> List[pathlib.Path]: + # Check if the path is of a directory or a file: + if data_path.is_dir(): + # Get all files inside the directory: + text_files = list(data_path.glob("*.*")) + elif data_path.is_file(): + text_files = [data_path] + else: + raise ValueError( + f"Unrecognized data path. The parameter `data_path` must be either a directory path or a file path. " + f"Given: {str(data_path)} " + ) + + return text_files + + +def _get_translation_pipeline( + model_name: str = None, + source_language: str = None, + target_language: str = None, + device: str = None, + model_kwargs: dict = None, + batch_size: int = None, +) -> Tuple[transformers.Pipeline, str]: + # Construct the model name - if model name is provided (not None) then we take it, otherwise we check both source + # and target were provided to construct the model name: + if model_name is None and (source_language is None or target_language is None): + raise ValueError( + "No model name were given and missing source and / or target languages. In order to translate you must " + "pass a `model_name` or both `source_language` and `target_language`." + ) + elif model_name is None: + model_name = f"Helsinki-NLP/opus-mt-{source_language}-{target_language}" + + # Initialize the translation pipeline: + try: + translation_pipeline = transformers.pipeline( + task="translation", + model=model_name, + tokenizer=model_name, + device=device, + model_kwargs=model_kwargs, + batch_size=batch_size, + ) + except OSError as load_exception: + if ( + "is not a valid model identifier listed on 'https://huggingface.co/models'" + in str(load_exception) + and source_language + ): + raise ValueError( + f"The model '{model_name}' is not a valid model identifier. " + f"The parameters `source_language` and `target_language` are used to construct a Helsinki model for " + f"text to text generation, but the model created from the given languages does not exist. " + f"You may check language identifiers at " + f"https://developers.google.com/admin-sdk/directory/v1/languages, and if the error was not fixed, one " + f"or more language code might be with 3 letters and needs to be found online. " + f"Remember, you can always choose a model directly from the Huggingface hub by using the `model_name` " + f"parameter." + ) from load_exception + raise load_exception + + return translation_pipeline, model_name + + +def _translate( + text_file: pathlib.Path, + translation_pipeline: transformers.Pipeline, + translation_kwargs: dict, +) -> str: + # Read the text from file: + with open(text_file, "r") as fp: + text = fp.read() + + # Split to paragraphs and each paragraph to sentences: + paragraphs = [paragraph.split(".") for paragraph in text.split("\n")] + + # Discover the newline indexes to restore the file to its structure post translation: + newlines_indexes = [] + for paragraph in paragraphs[:-1]: + if len(newlines_indexes) == 0: + newlines_indexes.append(len(paragraph) - 1) + else: + newlines_indexes.append(newlines_indexes[-1] + len(paragraph)) + + # Prepare the batches (each sentence from the paragraphs). Notice we add a dot not only to restore the sentence + # structure but to ignore empty strings as it will ruin the translation: + sentences = [f"{line}." for paragraph in paragraphs for line in paragraph] + + # Translate the sentences: + translations = translation_pipeline(sentences, **translation_kwargs) + + # Restructure the full text from the sentences: + translated_text = [] + newline_index = newlines_indexes.pop(0) if newlines_indexes else None + for i, translation in enumerate(translations): + # Get the translation: + text = translation["translation_text"] + # Validate if it was an empty sentence before: + if text == ".": + text = "" + # Check if needed to insert a newline: + if newline_index and newline_index == i: + text += "\n" + newline_index = newlines_indexes.pop(0) if newlines_indexes else None + # Collect it: + translated_text.append(text) + translated_text = "".join(translated_text) + + return translated_text + + +def _save_to_file( + translation: str, file_name: str, output_directory: pathlib.Path +) -> pathlib.Path: + # Prepare the file full path (checking for no duplications): + translation_file = output_directory / f"{file_name}.txt" + i = 1 + while translation_file.exists(): + i += 1 + translation_file = output_directory / f"{file_name}_{i}.txt" + + # Make sure all directories are created: + translation_file.parent.mkdir(exist_ok=True, parents=True) + + # Write to file: + with open(translation_file, "w") as fp: + fp.write(translation) + + return translation_file +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + \ No newline at end of file diff --git a/functions/master/translate/latest/src/function.yaml b/functions/master/translate/latest/src/function.yaml index bb165610..9595b77a 100644 --- a/functions/master/translate/latest/src/function.yaml +++ b/functions/master/translate/latest/src/function.yaml @@ -1,77 +1,36 @@ -kind: job -metadata: - name: translate - tag: '' - hash: 7eedf684bcebfbfd964e5503afbb56335c8f4097 - project: '' - labels: - author: guyl - categories: - - data-preparation - - huggingface - - machine-learning - - deep-learning - - NLP spec: - command: '' - args: [] - image: '' - build: - functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK - base_image: mlrun/mlrun - commands: [] - code_origin: '' - origin_filename: '' - requirements: - - transformers - - sentencepiece - - torch - - tqdm entry_points: open_mpi_handler: - name: open_mpi_handler - doc: '' + lineno: 56 parameters: - name: worker_inputs type: List[str] - name: root_worker_inputs type: Dict[str, Any] default: null - outputs: [] - lineno: 56 - has_varargs: false + name: open_mpi_handler has_kwargs: false - decorator: - name: decorator doc: '' + has_varargs: false + decorator: + lineno: 68 parameters: - name: handler - outputs: [] - lineno: 68 - has_varargs: false + name: decorator has_kwargs: false + doc: '' + has_varargs: false wrapper: + lineno: 73 name: wrapper + has_kwargs: true doc: '' - parameters: [] - outputs: [] - lineno: 73 has_varargs: false - has_kwargs: true translate: - name: translate - doc: 'Translate text files using a transformer model from Huggingface''s hub - according to the source and target languages - - given (or using the directly provided model name). The end result is a directory - of translated text files and a - - dataframe containing the following columns: - - - * text_file - The text file path. - - * translation_file - The translation text file name in the output directory.' + outputs: + - doc: 'A tuple of:' + type: Tuple[str, pd.DataFrame, dict] + lineno: 135 parameters: - name: data_path type: Union[str, List[str], Path] @@ -116,20 +75,41 @@ spec: type: bool doc: 'Whether to present logs of a progress bar and errors. Default: True.' default: false - outputs: - - doc: 'A tuple of:' - type: Tuple[str, pd.DataFrame, dict] - lineno: 135 - has_varargs: false + name: translate has_kwargs: false - description: Translate text files from one language to another + doc: 'Translate text files using a transformer model from Huggingface''s hub + according to the source and target languages + + given (or using the directly provided model name). The end result is a directory + of translated text files and a + + dataframe containing the following columns: + + + * text_file - The text file path. + + * translation_file - The translation text file name in the output directory.' + has_varargs: false + build: + requirements: + - transformers + - sentencepiece + - torch + - tqdm + code_origin: '' + functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK + base_image: mlrun/mlrun + origin_filename: '' + image: '' default_handler: translate disable_auto_mount: false - clone_target_dir: '' - env: [] - priority_class_name: '' - preemption_mode: prevent - affinity: null - tolerations: null - security_context: {} + command: '' + description: Translate text files from one language to another verbose: false +metadata: + categories: + - genai + - NLP + tag: '' + name: translate +kind: job diff --git a/functions/master/translate/latest/src/item.yaml b/functions/master/translate/latest/src/item.yaml index e6394734..839d1efa 100644 --- a/functions/master/translate/latest/src/item.yaml +++ b/functions/master/translate/latest/src/item.yaml @@ -1,9 +1,6 @@ apiVersion: v1 categories: -- data-preparation -- huggingface -- machine-learning -- deep-learning +- genai - NLP description: Translate text files from one language to another doc: '' @@ -15,7 +12,7 @@ labels: author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.1 +mlrunVersion: 1.7.0 name: translate platformVersion: 3.5.3 spec: @@ -29,5 +26,5 @@ spec: - torch - tqdm url: '' -version: 0.1.0 +version: 0.2.0 test_valid: True diff --git a/functions/master/translate/latest/static/documentation.html b/functions/master/translate/latest/static/documentation.html index 237b2231..9e4fdd01 100644 --- a/functions/master/translate/latest/static/documentation.html +++ b/functions/master/translate/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/translate/latest/static/example.html b/functions/master/translate/latest/static/example.html index e763bd83..6b086df4 100644 --- a/functions/master/translate/latest/static/example.html +++ b/functions/master/translate/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/translate/latest/static/function.html b/functions/master/translate/latest/static/function.html index ca69dbf3..38951174 100644 --- a/functions/master/translate/latest/static/function.html +++ b/functions/master/translate/latest/static/function.html @@ -28,80 +28,39 @@
             
    -kind: job
    -metadata:
    -  name: translate
    -  tag: ''
    -  hash: 7eedf684bcebfbfd964e5503afbb56335c8f4097
    -  project: ''
    -  labels:
    -    author: guyl
    -  categories:
    -  - data-preparation
    -  - huggingface
    -  - machine-learning
    -  - deep-learning
    -  - NLP
     spec:
    -  command: ''
    -  args: []
    -  image: ''
    -  build:
    -    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK
    -    base_image: mlrun/mlrun
    -    commands: []
    -    code_origin: ''
    -    origin_filename: ''
    -    requirements:
    -    - transformers
    -    - sentencepiece
    -    - torch
    -    - tqdm
       entry_points:
         open_mpi_handler:
    -      name: open_mpi_handler
    -      doc: ''
    +      lineno: 56
           parameters:
           - name: worker_inputs
             type: List[str]
           - name: root_worker_inputs
             type: Dict[str, Any]
             default: null
    -      outputs: []
    -      lineno: 56
    -      has_varargs: false
    +      name: open_mpi_handler
           has_kwargs: false
    -    decorator:
    -      name: decorator
           doc: ''
    +      has_varargs: false
    +    decorator:
    +      lineno: 68
           parameters:
           - name: handler
    -      outputs: []
    -      lineno: 68
    -      has_varargs: false
    +      name: decorator
           has_kwargs: false
    +      doc: ''
    +      has_varargs: false
         wrapper:
    +      lineno: 73
           name: wrapper
    +      has_kwargs: true
           doc: ''
    -      parameters: []
    -      outputs: []
    -      lineno: 73
           has_varargs: false
    -      has_kwargs: true
         translate:
    -      name: translate
    -      doc: 'Translate text files using a transformer model from Huggingface''s hub
    -        according to the source and target languages
    -
    -        given (or using the directly provided model name). The end result is a directory
    -        of translated text files and a
    -
    -        dataframe containing the following columns:
    -
    -
    -        * text_file - The text file path.
    -
    -        * translation_file - The translation text file name in the output directory.'
    +      outputs:
    +      - doc: 'A tuple of:'
    +        type: Tuple[str, pd.DataFrame, dict]
    +      lineno: 135
           parameters:
           - name: data_path
             type: Union[str, List[str], Path]
    @@ -146,23 +105,44 @@
             type: bool
             doc: 'Whether to present logs of a progress bar and errors. Default: True.'
             default: false
    -      outputs:
    -      - doc: 'A tuple of:'
    -        type: Tuple[str, pd.DataFrame, dict]
    -      lineno: 135
    -      has_varargs: false
    +      name: translate
           has_kwargs: false
    -  description: Translate text files from one language to another
    +      doc: 'Translate text files using a transformer model from Huggingface''s hub
    +        according to the source and target languages
    +
    +        given (or using the directly provided model name). The end result is a directory
    +        of translated text files and a
    +
    +        dataframe containing the following columns:
    +
    +
    +        * text_file - The text file path.
    +
    +        * translation_file - The translation text file name in the output directory.'
    +      has_varargs: false
    +  build:
    +    requirements:
    +    - transformers
    +    - sentencepiece
    +    - torch
    +    - tqdm
    +    code_origin: ''
    +    functionSourceCode: IyBDb3B5cmlnaHQgMjAyMyBJZ3VhemlvCiMKIyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiMgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CiMKIyAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAojCiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQojIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiMgU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAojIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IG9wZXJhdG9yCmltcG9ydCBwYXRobGliCmZyb20gZnVuY3Rvb2xzIGltcG9ydCByZWR1Y2UsIHdyYXBzCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIFR1cGxlLCBVbmlvbgoKaW1wb3J0IHBhbmRhcyBhcyBwZAppbXBvcnQgdHJhbnNmb3JtZXJzCmZyb20gdHFkbSBpbXBvcnQgdHFkbQoKIyBHZXQgdGhlIGdsb2JhbCBsb2dnZXI6Cl9MT0dHRVIgPSBsb2dnaW5nLmdldExvZ2dlcigpCgoKZGVmIF9jaGVja19tbHJ1bl9hbmRfb3Blbl9tcGkoKSAtPiBUdXBsZVsibWxydW4uTUxDbGllbnRDdHgiLCAibXBpNHB5Lk1QSS5JbnRyYWNvbW0iXToKICAgIGlzX21waSA9IEZhbHNlCiAgICB0cnk6CiAgICAgICAgaW1wb3J0IG1scnVuCgogICAgICAgIGNvbnRleHQgPSBtbHJ1bi5nZXRfb3JfY3JlYXRlX2N0eChuYW1lPSJtbHJ1biIpCiAgICAgICAgaXNfbXBpID0gY29udGV4dC5sYWJlbHMuZ2V0KCJraW5kIiwgImpvYiIpID09ICJtcGlqb2IiCgogICAgICAgIGlmIGlzX21waToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZnJvbSBtcGk0cHkgaW1wb3J0IE1QSQoKICAgICAgICAgICAgICAgIHJldHVybiBjb250ZXh0LCBNUEkuQ09NTV9XT1JMRAogICAgICAgICAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvciBhcyBtcGk0cHlfbm90X2ZvdW5kOgogICAgICAgICAgICAgICAgY29udGV4dC5sb2dnZXIuZXJyb3IoCiAgICAgICAgICAgICAgICAgICAgIlRvIGRpc3RyaWJ1dGUgdGhlIGZ1bmN0aW9uIHVzaW5nIE1MUnVuJ3MgJ21waWpvYicgeW91IG5lZWQgdG8gaGF2ZSBgbXBpNHB5YCBwYWNrYWdlIGluIHlvdXIgIgogICAgICAgICAgICAgICAgICAgICJpbnRlcnByZXRlci4gUGxlYXNlIHJ1biBgcGlwIGluc3RhbGwgbXBpNHB5YCBhbmQgbWFrZSBzdXJlIHlvdSBoYXZlIG9wZW4tbXBpLiIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHJhaXNlIG1waTRweV9ub3RfZm91bmQKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gY29udGV4dCwgTm9uZQogICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3IgYXMgbW9kdWxlX25vdF9mb3VuZDoKICAgICAgICBpZiBpc19tcGk6CiAgICAgICAgICAgIHJhaXNlIG1vZHVsZV9ub3RfZm91bmQKICAgIHJldHVybiBOb25lLCBOb25lCgoKZGVmIG9wZW5fbXBpX2hhbmRsZXIoCiAgICB3b3JrZXJfaW5wdXRzOiBMaXN0W3N0cl0sIHJvb3Rfd29ya2VyX2lucHV0czogRGljdFtzdHIsIEFueV0gPSBOb25lCik6CiAgICBnbG9iYWwgX0xPR0dFUgoKICAgICMgQ2hlY2sgZm9yIE1MUnVuIGFuZCBPcGVuTVBJIGF2YWlsYWJpbGl0eToKICAgIGNvbnRleHQsIGNvbW0gPSBfY2hlY2tfbWxydW5fYW5kX29wZW5fbXBpKCkKCiAgICAjIENoZWNrIGlmIE1MUnVuIGlzIGF2YWlsYWJsZSwgc2V0IHRoZSBnbG9iYWwgbG9nZ2VyIHRvIE1MUnVuJ3M6CiAgICBpZiBjb250ZXh0OgogICAgICAgIF9MT0dHRVIgPSBjb250ZXh0LmxvZ2dlcgoKICAgIGRlZiBkZWNvcmF0b3IoaGFuZGxlcik6CiAgICAgICAgaWYgY29tbSBpcyBOb25lIG9yIGNvbW0uR2V0X3NpemUoKSA9PSAxOgogICAgICAgICAgICByZXR1cm4gaGFuZGxlcgoKICAgICAgICBAd3JhcHMoaGFuZGxlcikKICAgICAgICBkZWYgd3JhcHBlcigqKmt3YXJncyk6CiAgICAgICAgICAgICMgR2V0IHRoZSBvcGVuIG1waSBlbnZpcm9ubWVudCBwcm9wZXJ0aWVzOgogICAgICAgICAgICBzaXplID0gY29tbS5HZXRfc2l6ZSgpCiAgICAgICAgICAgIHJhbmsgPSBjb21tLkdldF9yYW5rKCkKCiAgICAgICAgICAgICMgR2l2ZSB0aGUgY29ycmVjdCBjaHVuayBvZiB0aGUgd29ya2VycyBpbnB1dHM6CiAgICAgICAgICAgIGZvciB3b3JrZXJfaW5wdXQgaW4gd29ya2VyX2lucHV0czoKICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0ga3dhcmdzW3dvcmtlcl9pbnB1dF0KICAgICAgICAgICAgICAgIGlmIGlucHV0X2FyZ3VtZW50IGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIChzdHIsIHBhdGhsaWIuUGF0aCkpOgogICAgICAgICAgICAgICAgICAgIGlucHV0X2FyZ3VtZW50ID0gX2dldF90ZXh0X2ZpbGVzKAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhX3BhdGg9cGF0aGxpYi5QYXRoKGlucHV0X2FyZ3VtZW50KS5hYnNvbHV0ZSgpCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgbGVuKGlucHV0X2FyZ3VtZW50KSA8IHNpemU6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgZiJDYW5ub3Qgc3BsaXQgdGhlIGlucHV0ICd7d29ya2VyX2lucHV0fScgb2YgbGVuZ3RoIHtsZW4oaW5wdXRfYXJndW1lbnQpfSB0byB7c2l6ZX0gd29ya2Vycy4gIgogICAgICAgICAgICAgICAgICAgICAgICBmIlBsZWFzZSByZWR1Y2UgdGhlIGFtb3VudCBvZiB3b3JrZXJzIGZvciB0aGlzIGlucHV0LiIKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICBldmVuX2NodW5rX3NpemUgPSBsZW4oaW5wdXRfYXJndW1lbnQpIC8vIHNpemUKICAgICAgICAgICAgICAgIGNodW5rX3N0YXJ0ID0gcmFuayAqIGV2ZW5fY2h1bmtfc2l6ZQogICAgICAgICAgICAgICAgY2h1bmtfZW5kID0gKAogICAgICAgICAgICAgICAgICAgIChyYW5rICsgMSkgKiBldmVuX2NodW5rX3NpemUKICAgICAgICAgICAgICAgICAgICBpZiByYW5rICsgMSA8IHNpemUKICAgICAgICAgICAgICAgICAgICBlbHNlIGxlbihpbnB1dF9hcmd1bWVudCkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGNvbnRleHQubG9nZ2VyLmluZm8oCiAgICAgICAgICAgICAgICAgICAgZiJSYW5rICN7cmFua306IFByb2Nlc3NpbmcgaW5wdXQgY2h1bmsgb2YgJ3t3b3JrZXJfaW5wdXR9JyAiCiAgICAgICAgICAgICAgICAgICAgZiJmcm9tIGluZGV4IHtjaHVua19zdGFydH0gdG8ge2NodW5rX2VuZH0uIgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShpbnB1dF9hcmd1bWVudCwgbGlzdCk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudFtjaHVua19zdGFydDpjaHVua19lbmRdCiAgICAgICAgICAgICAgICBlbGlmIGlzaW5zdGFuY2UoaW5wdXRfYXJndW1lbnQsIHBkLkRhdGFGcmFtZSk6CiAgICAgICAgICAgICAgICAgICAgaW5wdXRfYXJndW1lbnQgPSBpbnB1dF9hcmd1bWVudC5pbG9jW2NodW5rX3N0YXJ0OmNodW5rX2VuZDosIDpdCiAgICAgICAgICAgICAgICBrd2FyZ3Nbd29ya2VyX2lucHV0XSA9IGlucHV0X2FyZ3VtZW50CgogICAgICAgICAgICAjIFNldCB0aGUgcm9vdCB3b3JrZXIgb25seSBhcmd1bWVudHM6CiAgICAgICAgICAgIGlmIHJhbmsgPT0gMCBhbmQgcm9vdF93b3JrZXJfaW5wdXRzOgogICAgICAgICAgICAgICAga3dhcmdzLnVwZGF0ZShyb290X3dvcmtlcl9pbnB1dHMpCgogICAgICAgICAgICAjIFJ1biB0aGUgd29ya2VyOgogICAgICAgICAgICBvdXRwdXQgPSBoYW5kbGVyKCoqa3dhcmdzKQoKICAgICAgICAgICAgIyBTZW5kIHRoZSBvdXRwdXQgdG8gdGhlIHJvb3QgcmFuayAocmFuayAjMCk6CiAgICAgICAgICAgIG91dHB1dCA9IGNvbW0uZ2F0aGVyKG91dHB1dCwgcm9vdD0wKQogICAgICAgICAgICBpZiByYW5rID09IDA6CiAgICAgICAgICAgICAgICAjIEpvaW4gdGhlIG91dHB1dHM6CiAgICAgICAgICAgICAgICBjb250ZXh0LmxvZ2dlci5pbmZvKCJDb2xsZWN0aW5nIGRhdGEgZnJvbSB3b3JrZXJzIHRvIHJvb3Qgd29ya2VyLiIpCiAgICAgICAgICAgICAgICBvdXRwdXRfZGlyZWN0b3J5ID0gb3V0cHV0WzBdWzBdCiAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBwZC5jb25jYXQob2Jqcz1bZGYgZm9yIF8sIGRmLCBfIGluIG91dHB1dF0sIGF4aXM9MCkKICAgICAgICAgICAgICAgIGVycm9yc19kaWN0aW9uYXJ5ID0gcmVkdWNlKAogICAgICAgICAgICAgICAgICAgIG9wZXJhdG9yLmlvciwgW2VyciBmb3IgXywgXywgZXJyIGluIG91dHB1dF0sIHt9CiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICByZXR1cm4gb3V0cHV0X2RpcmVjdG9yeSwgZGF0YWZyYW1lLCBlcnJvcnNfZGljdGlvbmFyeQogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICByZXR1cm4gd3JhcHBlcgoKICAgIHJldHVybiBkZWNvcmF0b3IKCgpAb3Blbl9tcGlfaGFuZGxlcih3b3JrZXJfaW5wdXRzPVsiZGF0YV9wYXRoIl0sIHJvb3Rfd29ya2VyX2lucHV0cz17InZlcmJvc2UiOiBUcnVlfSkKZGVmIHRyYW5zbGF0ZSgKICAgIGRhdGFfcGF0aDogVW5pb25bc3RyLCBMaXN0W3N0cl0sIHBhdGhsaWIuUGF0aF0sCiAgICBvdXRwdXRfZGlyZWN0b3J5OiBzdHIsCiAgICBtb2RlbF9uYW1lOiBzdHIgPSBOb25lLAogICAgc291cmNlX2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgdGFyZ2V0X2xhbmd1YWdlOiBzdHIgPSBOb25lLAogICAgZGV2aWNlOiBzdHIgPSBOb25lLAogICAgbW9kZWxfa3dhcmdzOiBkaWN0ID0gTm9uZSwKICAgIGJhdGNoX3NpemU6IGludCA9IDEsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QgPSBOb25lLAogICAgdmVyYm9zZTogYm9vbCA9IEZhbHNlLAopIC0+IFR1cGxlW3N0ciwgcGQuRGF0YUZyYW1lLCBkaWN0XToKICAgICIiIgogICAgVHJhbnNsYXRlIHRleHQgZmlsZXMgdXNpbmcgYSB0cmFuc2Zvcm1lciBtb2RlbCBmcm9tIEh1Z2dpbmdmYWNlJ3MgaHViIGFjY29yZGluZyB0byB0aGUgc291cmNlIGFuZCB0YXJnZXQgbGFuZ3VhZ2VzCiAgICBnaXZlbiAob3IgdXNpbmcgdGhlIGRpcmVjdGx5IHByb3ZpZGVkIG1vZGVsIG5hbWUpLiBUaGUgZW5kIHJlc3VsdCBpcyBhIGRpcmVjdG9yeSBvZiB0cmFuc2xhdGVkIHRleHQgZmlsZXMgYW5kIGEKICAgIGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiAgICAqIHRleHRfZmlsZSAtIFRoZSB0ZXh0IGZpbGUgcGF0aC4KICAgICogdHJhbnNsYXRpb25fZmlsZSAtIFRoZSB0cmFuc2xhdGlvbiB0ZXh0IGZpbGUgbmFtZSBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeS4KCiAgICA6cGFyYW0gZGF0YV9wYXRoOiAgICAgICAgICBBIGRpcmVjdG9yeSBvZiB0ZXh0IGZpbGVzIG9yIGEgc2luZ2xlIGZpbGUgb3IgYSBsaXN0IG9mIGZpbGVzIHRvIHRyYW5zbGF0ZS4KICAgIDpwYXJhbSBvdXRwdXRfZGlyZWN0b3J5OiAgIERpcmVjdG9yeSB3aGVyZSB0aGUgdHJhbnNsYXRlZCBmaWxlcyB3aWxsIGJlIHNhdmVkLgogICAgOnBhcmFtIG1vZGVsX25hbWU6ICAgICAgICAgVGhlIG5hbWUgb2YgYSBtb2RlbCB0byBsb2FkLiBJZiBOb25lLCB0aGUgbW9kZWwgbmFtZSBpcyBjb25zdHJ1Y3RlZCB1c2luZyB0aGUgc291cmNlIGFuZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IGxhbmd1YWdlcyBwYXJhbWV0ZXJzLgogICAgOnBhcmFtIHNvdXJjZV9sYW5ndWFnZTogICAgVGhlIHNvdXJjZSBsYW5ndWFnZSBjb2RlIChlLmcuLCAnZW4nIGZvciBFbmdsaXNoKS4KICAgIDpwYXJhbSB0YXJnZXRfbGFuZ3VhZ2U6ICAgIFRoZSB0YXJnZXQgbGFuZ3VhZ2UgY29kZSAoZS5nLiwgJ2VuJyBmb3IgRW5nbGlzaCkuCiAgICA6cGFyYW0gbW9kZWxfa3dhcmdzOiAgICAgICBLZXl3b3JkIGFyZ3VtZW50cyB0byBwYXNzIHJlZ2FyZGluZyB0aGUgbG9hZGluZyBvZiB0aGUgbW9kZWwgaW4gSHVnZ2luZ0ZhY2UncyBgcGlwZWxpbmVgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbi4KICAgIDpwYXJhbSBkZXZpY2U6ICAgICAgICAgICAgIFRoZSBkZXZpY2UgaW5kZXggZm9yIHRyYW5zZm9ybWVycy4gRGVmYXVsdCB3aWxsIHByZWZlciBjdWRhIGlmIGF2YWlsYWJsZS4KICAgIDpwYXJhbSBiYXRjaF9zaXplOiAgICAgICAgIFRoZSBudW1iZXIgb2YgYmF0Y2hlcyB0byB1c2UgaW4gdHJhbnNsYXRpb24uIFRoZSBmaWxlcyBhcmUgdHJhbnNsYXRlZCBvbmUgYnkgb25lLCBidXQgdGhlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW50ZW5jZXMgY2FuIGJlIGJhdGNoZWQuCiAgICA6cGFyYW0gdHJhbnNsYXRpb25fa3dhcmdzOiBBZGRpdGlvbmFsIGtleXdvcmQgYXJndW1lbnRzIHRvIHBhc3MgdG8gYSBgdHJhbnNmb3JtZXJzLlRyYW5zbGF0aW9uUGlwZWxpbmVgIHdoZW4gZG9pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSB0cmFuc2xhdGlvbiBpbmZlcmVuY2UuIE5vdGljZSB0aGUgYmF0Y2ggc2l6ZSBoZXJlIGlzIGJlaW5nIGFkZGVkIGF1dG9tYXRpY2FsbHkuCiAgICA6cGFyYW0gdmVyYm9zZTogICAgICAgICAgICBXaGV0aGVyIHRvIHByZXNlbnQgbG9ncyBvZiBhIHByb2dyZXNzIGJhciBhbmQgZXJyb3JzLiBEZWZhdWx0OiBUcnVlLgoKICAgIDpyZXR1cm5zOiBBIHR1cGxlIG9mOgoKICAgICAgICAgICAgICAqIFBhdGggdG8gdGhlIG91dHB1dCBkaXJlY3RvcnkuCiAgICAgICAgICAgICAgKiBBIGRhdGFmcmFtZSBkYXRhc2V0IG9mIHRoZSB0cmFuc2xhdGVkIGZpbGUgbmFtZXMuCiAgICAgICAgICAgICAgKiBBIGRpY3Rpb25hcnkgb2YgZXJyb3JlZCBmaWxlcyB0aGF0IHdlcmUgbm90IHRyYW5zbGF0ZWQuCiAgICAiIiIKICAgIGdsb2JhbCBfTE9HR0VSCgogICAgIyBHZXQgdGhlIGlucHV0IHRleHQgZmlsZXMgdG8gdHJhbnNsYXRlOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oIkNvbGxlY3RpbmcgdGV4dCBmaWxlcy4iKQogICAgaWYgaXNpbnN0YW5jZShkYXRhX3BhdGgsIHN0cik6CiAgICAgICAgZGF0YV9wYXRoID0gcGF0aGxpYi5QYXRoKGRhdGFfcGF0aCkuYWJzb2x1dGUoKQogICAgICAgIHRleHRfZmlsZXMgPSBfZ2V0X3RleHRfZmlsZXMoZGF0YV9wYXRoPWRhdGFfcGF0aCkKICAgIGVsc2U6CiAgICAgICAgdGV4dF9maWxlcyA9IGRhdGFfcGF0aAogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oZiJDb2xsZWN0ZWQge2xlbih0ZXh0X2ZpbGVzKX0gdGV4dCBmaWxlcy4iKQoKICAgICMgR2V0IHRoZSB0cmFuc2xhdGlvbiBwaXBlbGluZToKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgX0xPR0dFUi5pbmZvKGYiTG9hZGluZyBtb2RlbCAtIHVzaW5nIGRldmljZSAne2RldmljZX0nLiIpCiAgICB0cmFuc2xhdGlvbl9waXBlbGluZSwgbW9kZWxfbmFtZSA9IF9nZXRfdHJhbnNsYXRpb25fcGlwZWxpbmUoCiAgICAgICAgbW9kZWxfbmFtZT1tb2RlbF9uYW1lLAogICAgICAgIHNvdXJjZV9sYW5ndWFnZT1zb3VyY2VfbGFuZ3VhZ2UsCiAgICAgICAgdGFyZ2V0X2xhbmd1YWdlPXRhcmdldF9sYW5ndWFnZSwKICAgICAgICBkZXZpY2U9ZGV2aWNlLAogICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgYmF0Y2hfc2l6ZT1iYXRjaF9zaXplIGlmIGJhdGNoX3NpemUgIT0gMSBlbHNlIE5vbmUsCiAgICApCiAgICBpZiB2ZXJib3NlOgogICAgICAgIF9MT0dHRVIuaW5mbyhmIk1vZGVsICd7bW9kZWxfbmFtZX0nIHdhcyBsb2FkZWQgc3VjY2Vzc2Z1bGx5LiIpCgogICAgIyBQcmVwYXJlIHRoZSBzdWNjZXNzZXMgZGF0YWZyYW1lIGFuZCBlcnJvcnMgZGljdGlvbmFyeSB0byBiZSByZXR1cm5lZDoKICAgIHN1Y2Nlc3NlcyA9IFtdCiAgICBlcnJvcnMgPSB7fQoKICAgICMgQ3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5OgogICAgb3V0cHV0X2RpcmVjdG9yeSA9IHBhdGhsaWIuUGF0aChvdXRwdXRfZGlyZWN0b3J5KQogICAgb3V0cHV0X2RpcmVjdG9yeS5ta2RpcihwYXJlbnRzPVRydWUsIGV4aXN0X29rPVRydWUpCgogICAgIyBQcmVwYXJlIHRoZSB0cmFuc2xhdGlvbiBrZXl3b3JkIGFyZ3VtZW50czoKICAgIHRyYW5zbGF0aW9uX2t3YXJncyA9IHRyYW5zbGF0aW9uX2t3YXJncyBvciB7fQoKICAgICMgR28gb3ZlciB0aGUgYXVkaW8gZmlsZXMgYW5kIHRyYW5zY3JpYmU6CiAgICBmb3IgdGV4dF9maWxlIGluIHRxZG0oCiAgICAgICAgdGV4dF9maWxlcywgZGVzYz0iVHJhbnNsYXRpbmciLCB1bml0PSJmaWxlIiwgZGlzYWJsZT1ub3QgdmVyYm9zZQogICAgKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMgVHJhbnNsYXRlOgogICAgICAgICAgICB0cmFuc2xhdGlvbiA9IF90cmFuc2xhdGUoCiAgICAgICAgICAgICAgICB0ZXh0X2ZpbGU9dGV4dF9maWxlLAogICAgICAgICAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmU9dHJhbnNsYXRpb25fcGlwZWxpbmUsCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbl9rd2FyZ3M9dHJhbnNsYXRpb25fa3dhcmdzLAogICAgICAgICAgICApCiAgICAgICAgICAgICMgV3JpdGUgdGhlIHRyYW5zY3JpcHRpb24gdG8gZmlsZToKICAgICAgICAgICAgdHJhbnNsYXRpb25fZmlsZSA9IF9zYXZlX3RvX2ZpbGUoCiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvbj10cmFuc2xhdGlvbiwKICAgICAgICAgICAgICAgIGZpbGVfbmFtZT10ZXh0X2ZpbGUuc3RlbSwKICAgICAgICAgICAgICAgIG91dHB1dF9kaXJlY3Rvcnk9b3V0cHV0X2RpcmVjdG9yeSwKICAgICAgICAgICAgKQogICAgICAgICAgICAjIE5vdGUgYXMgYSBzdWNjZXNzIGluIHRoZSBsaXN0OgogICAgICAgICAgICBzdWNjZXNzZXMuYXBwZW5kKAogICAgICAgICAgICAgICAgWwogICAgICAgICAgICAgICAgICAgIHRleHRfZmlsZS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uX2ZpbGUubmFtZSwKICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXhjZXB0aW9uOgogICAgICAgICAgICAjIE5vdGUgdGhlIGV4Y2VwdGlvbiBhcyBlcnJvciBpbiB0aGUgZGljdGlvbmFyeToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIF9MT0dHRVIud2FybmluZyhmIkVycm9yIGluIGZpbGU6ICd7dGV4dF9maWxlLm5hbWV9JyIpCiAgICAgICAgICAgIGVycm9yc1tzdHIodGV4dF9maWxlLm5hbWUpXSA9IHN0cihleGNlcHRpb24pCiAgICAgICAgICAgIGNvbnRpbnVlCgogICAgIyBDb25zdHJ1Y3QgdGhlIHRyYW5zbGF0aW9ucyBkYXRhZnJhbWU6CiAgICBjb2x1bW5zID0gWwogICAgICAgICJ0ZXh0X2ZpbGUiLAogICAgICAgICJ0cmFuc2xhdGlvbl9maWxlIiwKICAgIF0KICAgIHN1Y2Nlc3NlcyA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBzdWNjZXNzZXMsCiAgICAgICAgY29sdW1ucz1jb2x1bW5zLAogICAgKQoKICAgICMgUHJpbnQgdGhlIGhlYWQgb2YgdGhlIHByb2R1Y2VkIGRhdGFmcmFtZSBhbmQgcmV0dXJuOgogICAgaWYgdmVyYm9zZToKICAgICAgICBfTE9HR0VSLmluZm8oCiAgICAgICAgICAgIGYiRG9uZSAoe3N1Y2Nlc3Nlcy5zaGFwZVswXX0ve2xlbih0ZXh0X2ZpbGVzKX0pXG4iCiAgICAgICAgICAgIGYiVHJhbnNsYXRpb25zIHN1bW1hcnk6XG4iCiAgICAgICAgICAgIGYie3N1Y2Nlc3Nlcy5oZWFkKCl9IgogICAgICAgICkKICAgIHJldHVybiBzdHIob3V0cHV0X2RpcmVjdG9yeSksIHN1Y2Nlc3NlcywgZXJyb3JzCgoKZGVmIF9nZXRfdGV4dF9maWxlcygKICAgIGRhdGFfcGF0aDogcGF0aGxpYi5QYXRoLAopIC0+IExpc3RbcGF0aGxpYi5QYXRoXToKICAgICMgQ2hlY2sgaWYgdGhlIHBhdGggaXMgb2YgYSBkaXJlY3Rvcnkgb3IgYSBmaWxlOgogICAgaWYgZGF0YV9wYXRoLmlzX2RpcigpOgogICAgICAgICMgR2V0IGFsbCBmaWxlcyBpbnNpZGUgdGhlIGRpcmVjdG9yeToKICAgICAgICB0ZXh0X2ZpbGVzID0gbGlzdChkYXRhX3BhdGguZ2xvYigiKi4qIikpCiAgICBlbGlmIGRhdGFfcGF0aC5pc19maWxlKCk6CiAgICAgICAgdGV4dF9maWxlcyA9IFtkYXRhX3BhdGhdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYiVW5yZWNvZ25pemVkIGRhdGEgcGF0aC4gVGhlIHBhcmFtZXRlciBgZGF0YV9wYXRoYCBtdXN0IGJlIGVpdGhlciBhIGRpcmVjdG9yeSBwYXRoIG9yIGEgZmlsZSBwYXRoLiAiCiAgICAgICAgICAgIGYiR2l2ZW46IHtzdHIoZGF0YV9wYXRoKX0gIgogICAgICAgICkKCiAgICByZXR1cm4gdGV4dF9maWxlcwoKCmRlZiBfZ2V0X3RyYW5zbGF0aW9uX3BpcGVsaW5lKAogICAgbW9kZWxfbmFtZTogc3RyID0gTm9uZSwKICAgIHNvdXJjZV9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIHRhcmdldF9sYW5ndWFnZTogc3RyID0gTm9uZSwKICAgIGRldmljZTogc3RyID0gTm9uZSwKICAgIG1vZGVsX2t3YXJnczogZGljdCA9IE5vbmUsCiAgICBiYXRjaF9zaXplOiBpbnQgPSBOb25lLAopIC0+IFR1cGxlW3RyYW5zZm9ybWVycy5QaXBlbGluZSwgc3RyXToKICAgICMgQ29uc3RydWN0IHRoZSBtb2RlbCBuYW1lIC0gaWYgbW9kZWwgbmFtZSBpcyBwcm92aWRlZCAobm90IE5vbmUpIHRoZW4gd2UgdGFrZSBpdCwgb3RoZXJ3aXNlIHdlIGNoZWNrIGJvdGggc291cmNlCiAgICAjIGFuZCB0YXJnZXQgd2VyZSBwcm92aWRlZCB0byBjb25zdHJ1Y3QgdGhlIG1vZGVsIG5hbWU6CiAgICBpZiBtb2RlbF9uYW1lIGlzIE5vbmUgYW5kIChzb3VyY2VfbGFuZ3VhZ2UgaXMgTm9uZSBvciB0YXJnZXRfbGFuZ3VhZ2UgaXMgTm9uZSk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigKICAgICAgICAgICAgIk5vIG1vZGVsIG5hbWUgd2VyZSBnaXZlbiBhbmQgbWlzc2luZyBzb3VyY2UgYW5kIC8gb3IgdGFyZ2V0IGxhbmd1YWdlcy4gSW4gb3JkZXIgdG8gdHJhbnNsYXRlIHlvdSBtdXN0ICIKICAgICAgICAgICAgInBhc3MgYSBgbW9kZWxfbmFtZWAgb3IgYm90aCBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAuIgogICAgICAgICkKICAgIGVsaWYgbW9kZWxfbmFtZSBpcyBOb25lOgogICAgICAgIG1vZGVsX25hbWUgPSBmIkhlbHNpbmtpLU5MUC9vcHVzLW10LXtzb3VyY2VfbGFuZ3VhZ2V9LXt0YXJnZXRfbGFuZ3VhZ2V9IgoKICAgICMgSW5pdGlhbGl6ZSB0aGUgdHJhbnNsYXRpb24gcGlwZWxpbmU6CiAgICB0cnk6CiAgICAgICAgdHJhbnNsYXRpb25fcGlwZWxpbmUgPSB0cmFuc2Zvcm1lcnMucGlwZWxpbmUoCiAgICAgICAgICAgIHRhc2s9InRyYW5zbGF0aW9uIiwKICAgICAgICAgICAgbW9kZWw9bW9kZWxfbmFtZSwKICAgICAgICAgICAgdG9rZW5pemVyPW1vZGVsX25hbWUsCiAgICAgICAgICAgIGRldmljZT1kZXZpY2UsCiAgICAgICAgICAgIG1vZGVsX2t3YXJncz1tb2RlbF9rd2FyZ3MsCiAgICAgICAgICAgIGJhdGNoX3NpemU9YmF0Y2hfc2l6ZSwKICAgICAgICApCiAgICBleGNlcHQgT1NFcnJvciBhcyBsb2FkX2V4Y2VwdGlvbjoKICAgICAgICBpZiAoCiAgICAgICAgICAgICJpcyBub3QgYSB2YWxpZCBtb2RlbCBpZGVudGlmaWVyIGxpc3RlZCBvbiAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9tb2RlbHMnIgogICAgICAgICAgICBpbiBzdHIobG9hZF9leGNlcHRpb24pCiAgICAgICAgICAgIGFuZCBzb3VyY2VfbGFuZ3VhZ2UKICAgICAgICApOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKAogICAgICAgICAgICAgICAgZiJUaGUgbW9kZWwgJ3ttb2RlbF9uYW1lfScgaXMgbm90IGEgdmFsaWQgbW9kZWwgaWRlbnRpZmllci4gIgogICAgICAgICAgICAgICAgZiJUaGUgcGFyYW1ldGVycyBgc291cmNlX2xhbmd1YWdlYCBhbmQgYHRhcmdldF9sYW5ndWFnZWAgYXJlIHVzZWQgdG8gY29uc3RydWN0IGEgSGVsc2lua2kgbW9kZWwgZm9yICIKICAgICAgICAgICAgICAgIGYidGV4dCB0byB0ZXh0IGdlbmVyYXRpb24sIGJ1dCB0aGUgbW9kZWwgY3JlYXRlZCBmcm9tIHRoZSBnaXZlbiBsYW5ndWFnZXMgZG9lcyBub3QgZXhpc3QuICIKICAgICAgICAgICAgICAgIGYiWW91IG1heSBjaGVjayBsYW5ndWFnZSBpZGVudGlmaWVycyBhdCAiCiAgICAgICAgICAgICAgICBmImh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL2FkbWluLXNkay9kaXJlY3RvcnkvdjEvbGFuZ3VhZ2VzLCBhbmQgaWYgdGhlIGVycm9yIHdhcyBub3QgZml4ZWQsIG9uZSAiCiAgICAgICAgICAgICAgICBmIm9yIG1vcmUgbGFuZ3VhZ2UgY29kZSBtaWdodCBiZSB3aXRoIDMgbGV0dGVycyBhbmQgbmVlZHMgdG8gYmUgZm91bmQgb25saW5lLiAiCiAgICAgICAgICAgICAgICBmIlJlbWVtYmVyLCB5b3UgY2FuIGFsd2F5cyBjaG9vc2UgYSBtb2RlbCBkaXJlY3RseSBmcm9tIHRoZSBIdWdnaW5nZmFjZSBodWIgYnkgdXNpbmcgdGhlIGBtb2RlbF9uYW1lYCAiCiAgICAgICAgICAgICAgICBmInBhcmFtZXRlci4iCiAgICAgICAgICAgICkgZnJvbSBsb2FkX2V4Y2VwdGlvbgogICAgICAgIHJhaXNlIGxvYWRfZXhjZXB0aW9uCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX3BpcGVsaW5lLCBtb2RlbF9uYW1lCgoKZGVmIF90cmFuc2xhdGUoCiAgICB0ZXh0X2ZpbGU6IHBhdGhsaWIuUGF0aCwKICAgIHRyYW5zbGF0aW9uX3BpcGVsaW5lOiB0cmFuc2Zvcm1lcnMuUGlwZWxpbmUsCiAgICB0cmFuc2xhdGlvbl9rd2FyZ3M6IGRpY3QsCikgLT4gc3RyOgogICAgIyBSZWFkIHRoZSB0ZXh0IGZyb20gZmlsZToKICAgIHdpdGggb3Blbih0ZXh0X2ZpbGUsICJyIikgYXMgZnA6CiAgICAgICAgdGV4dCA9IGZwLnJlYWQoKQoKICAgICMgU3BsaXQgdG8gcGFyYWdyYXBocyBhbmQgZWFjaCBwYXJhZ3JhcGggdG8gc2VudGVuY2VzOgogICAgcGFyYWdyYXBocyA9IFtwYXJhZ3JhcGguc3BsaXQoIi4iKSBmb3IgcGFyYWdyYXBoIGluIHRleHQuc3BsaXQoIlxuIildCgogICAgIyBEaXNjb3ZlciB0aGUgbmV3bGluZSBpbmRleGVzIHRvIHJlc3RvcmUgdGhlIGZpbGUgdG8gaXRzIHN0cnVjdHVyZSBwb3N0IHRyYW5zbGF0aW9uOgogICAgbmV3bGluZXNfaW5kZXhlcyA9IFtdCiAgICBmb3IgcGFyYWdyYXBoIGluIHBhcmFncmFwaHNbOi0xXToKICAgICAgICBpZiBsZW4obmV3bGluZXNfaW5kZXhlcykgPT0gMDoKICAgICAgICAgICAgbmV3bGluZXNfaW5kZXhlcy5hcHBlbmQobGVuKHBhcmFncmFwaCkgLSAxKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG5ld2xpbmVzX2luZGV4ZXMuYXBwZW5kKG5ld2xpbmVzX2luZGV4ZXNbLTFdICsgbGVuKHBhcmFncmFwaCkpCgogICAgIyBQcmVwYXJlIHRoZSBiYXRjaGVzIChlYWNoIHNlbnRlbmNlIGZyb20gdGhlIHBhcmFncmFwaHMpLiBOb3RpY2Ugd2UgYWRkIGEgZG90IG5vdCBvbmx5IHRvIHJlc3RvcmUgdGhlIHNlbnRlbmNlCiAgICAjIHN0cnVjdHVyZSBidXQgdG8gaWdub3JlIGVtcHR5IHN0cmluZ3MgYXMgaXQgd2lsbCBydWluIHRoZSB0cmFuc2xhdGlvbjoKICAgIHNlbnRlbmNlcyA9IFtmIntsaW5lfS4iIGZvciBwYXJhZ3JhcGggaW4gcGFyYWdyYXBocyBmb3IgbGluZSBpbiBwYXJhZ3JhcGhdCgogICAgIyBUcmFuc2xhdGUgdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0aW9ucyA9IHRyYW5zbGF0aW9uX3BpcGVsaW5lKHNlbnRlbmNlcywgKip0cmFuc2xhdGlvbl9rd2FyZ3MpCgogICAgIyBSZXN0cnVjdHVyZSB0aGUgZnVsbCB0ZXh0IGZyb20gdGhlIHNlbnRlbmNlczoKICAgIHRyYW5zbGF0ZWRfdGV4dCA9IFtdCiAgICBuZXdsaW5lX2luZGV4ID0gbmV3bGluZXNfaW5kZXhlcy5wb3AoMCkgaWYgbmV3bGluZXNfaW5kZXhlcyBlbHNlIE5vbmUKICAgIGZvciBpLCB0cmFuc2xhdGlvbiBpbiBlbnVtZXJhdGUodHJhbnNsYXRpb25zKToKICAgICAgICAjIEdldCB0aGUgdHJhbnNsYXRpb246CiAgICAgICAgdGV4dCA9IHRyYW5zbGF0aW9uWyJ0cmFuc2xhdGlvbl90ZXh0Il0KICAgICAgICAjIFZhbGlkYXRlIGlmIGl0IHdhcyBhbiBlbXB0eSBzZW50ZW5jZSBiZWZvcmU6CiAgICAgICAgaWYgdGV4dCA9PSAiLiI6CiAgICAgICAgICAgIHRleHQgPSAiIgogICAgICAgICMgQ2hlY2sgaWYgbmVlZGVkIHRvIGluc2VydCBhIG5ld2xpbmU6CiAgICAgICAgaWYgbmV3bGluZV9pbmRleCBhbmQgbmV3bGluZV9pbmRleCA9PSBpOgogICAgICAgICAgICB0ZXh0ICs9ICJcbiIKICAgICAgICAgICAgbmV3bGluZV9pbmRleCA9IG5ld2xpbmVzX2luZGV4ZXMucG9wKDApIGlmIG5ld2xpbmVzX2luZGV4ZXMgZWxzZSBOb25lCiAgICAgICAgIyBDb2xsZWN0IGl0OgogICAgICAgIHRyYW5zbGF0ZWRfdGV4dC5hcHBlbmQodGV4dCkKICAgIHRyYW5zbGF0ZWRfdGV4dCA9ICIiLmpvaW4odHJhbnNsYXRlZF90ZXh0KQoKICAgIHJldHVybiB0cmFuc2xhdGVkX3RleHQKCgpkZWYgX3NhdmVfdG9fZmlsZSgKICAgIHRyYW5zbGF0aW9uOiBzdHIsIGZpbGVfbmFtZTogc3RyLCBvdXRwdXRfZGlyZWN0b3J5OiBwYXRobGliLlBhdGgKKSAtPiBwYXRobGliLlBhdGg6CiAgICAjIFByZXBhcmUgdGhlIGZpbGUgZnVsbCBwYXRoIChjaGVja2luZyBmb3Igbm8gZHVwbGljYXRpb25zKToKICAgIHRyYW5zbGF0aW9uX2ZpbGUgPSBvdXRwdXRfZGlyZWN0b3J5IC8gZiJ7ZmlsZV9uYW1lfS50eHQiCiAgICBpID0gMQogICAgd2hpbGUgdHJhbnNsYXRpb25fZmlsZS5leGlzdHMoKToKICAgICAgICBpICs9IDEKICAgICAgICB0cmFuc2xhdGlvbl9maWxlID0gb3V0cHV0X2RpcmVjdG9yeSAvIGYie2ZpbGVfbmFtZX1fe2l9LnR4dCIKCiAgICAjIE1ha2Ugc3VyZSBhbGwgZGlyZWN0b3JpZXMgYXJlIGNyZWF0ZWQ6CiAgICB0cmFuc2xhdGlvbl9maWxlLnBhcmVudC5ta2RpcihleGlzdF9vaz1UcnVlLCBwYXJlbnRzPVRydWUpCgogICAgIyBXcml0ZSB0byBmaWxlOgogICAgd2l0aCBvcGVuKHRyYW5zbGF0aW9uX2ZpbGUsICJ3IikgYXMgZnA6CiAgICAgICAgZnAud3JpdGUodHJhbnNsYXRpb24pCgogICAgcmV0dXJuIHRyYW5zbGF0aW9uX2ZpbGUK
    +    base_image: mlrun/mlrun
    +    origin_filename: ''
    +  image: ''
       default_handler: translate
       disable_auto_mount: false
    -  clone_target_dir: ''
    -  env: []
    -  priority_class_name: ''
    -  preemption_mode: prevent
    -  affinity: null
    -  tolerations: null
    -  security_context: {}
    +  command: ''
    +  description: Translate text files from one language to another
     verbose: false
    +metadata:
    +  categories:
    +  - genai
    +  - NLP
    +  tag: ''
    +  name: translate
    +kind: job
     
             
         
    diff --git a/functions/master/translate/latest/static/item.html b/functions/master/translate/latest/static/item.html index 4e7aaf85..0c48dfc6 100644 --- a/functions/master/translate/latest/static/item.html +++ b/functions/master/translate/latest/static/item.html @@ -30,10 +30,7 @@ apiVersion: v1 categories: -- data-preparation -- huggingface -- machine-learning -- deep-learning +- genai - NLP description: Translate text files from one language to another doc: '' @@ -45,7 +42,7 @@ author: guyl maintainers: [] marketplaceType: '' -mlrunVersion: 1.5.1 +mlrunVersion: 1.7.0 name: translate platformVersion: 3.5.3 spec: @@ -59,7 +56,7 @@ - torch - tqdm url: '' -version: 0.1.0 +version: 0.2.0 test_valid: True diff --git a/functions/master/translate/latest/static/translate.html b/functions/master/translate/latest/static/translate.html index 1dc8ac2a..153b48a4 100644 --- a/functions/master/translate/latest/static/translate.html +++ b/functions/master/translate/latest/static/translate.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/1.2.0/static/documentation.html b/functions/master/v2_model_server/1.2.0/static/documentation.html index fc69d875..8c6b6a58 100644 --- a/functions/master/v2_model_server/1.2.0/static/documentation.html +++ b/functions/master/v2_model_server/1.2.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/1.2.0/static/example.html b/functions/master/v2_model_server/1.2.0/static/example.html index 6cc76013..c2a447d9 100644 --- a/functions/master/v2_model_server/1.2.0/static/example.html +++ b/functions/master/v2_model_server/1.2.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/1.2.0/static/v2_model_server.html b/functions/master/v2_model_server/1.2.0/static/v2_model_server.html index 9c6a96d5..2245fc64 100644 --- a/functions/master/v2_model_server/1.2.0/static/v2_model_server.html +++ b/functions/master/v2_model_server/1.2.0/static/v2_model_server.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/latest/static/documentation.html b/functions/master/v2_model_server/latest/static/documentation.html index fc69d875..8c6b6a58 100644 --- a/functions/master/v2_model_server/latest/static/documentation.html +++ b/functions/master/v2_model_server/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/latest/static/example.html b/functions/master/v2_model_server/latest/static/example.html index 6cc76013..c2a447d9 100644 --- a/functions/master/v2_model_server/latest/static/example.html +++ b/functions/master/v2_model_server/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_server/latest/static/v2_model_server.html b/functions/master/v2_model_server/latest/static/v2_model_server.html index 9c6a96d5..2245fc64 100644 --- a/functions/master/v2_model_server/latest/static/v2_model_server.html +++ b/functions/master/v2_model_server/latest/static/v2_model_server.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/1.1.0/static/documentation.html b/functions/master/v2_model_tester/1.1.0/static/documentation.html index 1252dca9..b7cbc37b 100644 --- a/functions/master/v2_model_tester/1.1.0/static/documentation.html +++ b/functions/master/v2_model_tester/1.1.0/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/1.1.0/static/example.html b/functions/master/v2_model_tester/1.1.0/static/example.html index f5d574bb..74c41d12 100644 --- a/functions/master/v2_model_tester/1.1.0/static/example.html +++ b/functions/master/v2_model_tester/1.1.0/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/1.1.0/static/v2_model_tester.html b/functions/master/v2_model_tester/1.1.0/static/v2_model_tester.html index 2eb9604e..996c4470 100644 --- a/functions/master/v2_model_tester/1.1.0/static/v2_model_tester.html +++ b/functions/master/v2_model_tester/1.1.0/static/v2_model_tester.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/latest/static/documentation.html b/functions/master/v2_model_tester/latest/static/documentation.html index 1252dca9..b7cbc37b 100644 --- a/functions/master/v2_model_tester/latest/static/documentation.html +++ b/functions/master/v2_model_tester/latest/static/documentation.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/latest/static/example.html b/functions/master/v2_model_tester/latest/static/example.html index f5d574bb..74c41d12 100644 --- a/functions/master/v2_model_tester/latest/static/example.html +++ b/functions/master/v2_model_tester/latest/static/example.html @@ -20,7 +20,7 @@ - + diff --git a/functions/master/v2_model_tester/latest/static/v2_model_tester.html b/functions/master/v2_model_tester/latest/static/v2_model_tester.html index 2eb9604e..996c4470 100644 --- a/functions/master/v2_model_tester/latest/static/v2_model_tester.html +++ b/functions/master/v2_model_tester/latest/static/v2_model_tester.html @@ -20,7 +20,7 @@ - +